From 62c768bdebe4d39e0d38bf8965e35201dda932cc Mon Sep 17 00:00:00 2001 From: hotwa Date: Tue, 26 Dec 2023 22:36:39 +0800 Subject: [PATCH] init --- spawnerdockerfile/Dockerfile | 75 +++++++++++++++++++ .../Dockerfile.base-notebook | 0 spawnerdockerfile/README.md | 23 ++++++ spawnerdockerfile/docker_healthcheck.py | 26 +++++++ spawnerdockerfile/jupyter_server_config.py | 58 ++++++++++++++ spawnerdockerfile/start-notebook.py | 41 ++++++++++ spawnerdockerfile/start-notebook.sh | 5 ++ spawnerdockerfile/start-singleuser.py | 23 ++++++ spawnerdockerfile/start-singleuser.sh | 5 ++ 9 files changed, 256 insertions(+) create mode 100644 spawnerdockerfile/Dockerfile rename Dockerfile.base-notebook => spawnerdockerfile/Dockerfile.base-notebook (100%) create mode 100644 spawnerdockerfile/README.md create mode 100755 spawnerdockerfile/docker_healthcheck.py create mode 100644 spawnerdockerfile/jupyter_server_config.py create mode 100755 spawnerdockerfile/start-notebook.py create mode 100755 spawnerdockerfile/start-notebook.sh create mode 100755 spawnerdockerfile/start-singleuser.py create mode 100755 spawnerdockerfile/start-singleuser.sh diff --git a/spawnerdockerfile/Dockerfile b/spawnerdockerfile/Dockerfile new file mode 100644 index 0000000..545e2db --- /dev/null +++ b/spawnerdockerfile/Dockerfile @@ -0,0 +1,75 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +ARG REGISTRY=quay.io +ARG OWNER=jupyter +ARG BASE_CONTAINER=$REGISTRY/$OWNER/docker-stacks-foundation +FROM $BASE_CONTAINER + +LABEL maintainer="Jupyter Project " + +# Fix: https://github.com/hadolint/hadolint/wiki/DL4006 +# Fix: https://github.com/koalaman/shellcheck/wiki/SC3014 +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +USER root + +# Install all OS dependencies for the Server that starts but lacks all +# features (e.g., download as all possible file formats) +RUN apt-get update --yes && \ + apt-get install --yes --no-install-recommends \ + fonts-liberation \ + # - pandoc is used to convert notebooks to html files + # it's not present in the aarch64 Ubuntu image, so we install it here + pandoc \ + # - run-one - a wrapper script that runs no more + # than one unique instance of some command with a unique set of arguments, + # we use `run-one-constantly` to support the `RESTARTABLE` option + run-one && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +USER ${NB_UID} + +# Install JupyterLab, Jupyter Notebook, JupyterHub and NBClassic +# Generate a Jupyter Server config +# Cleanup temporary files +# Correct permissions +# Do all this in a single RUN command to avoid duplicating all of the +# files across image layers when the permissions change +WORKDIR /tmp +RUN mamba install --yes \ + 'jupyterlab' \ + 'notebook' \ + 'jupyterhub' \ + 'nbclassic' && \ + jupyter server --generate-config && \ + mamba clean --all -f -y && \ + npm cache clean --force && \ + jupyter lab clean && \ + rm -rf "/home/${NB_USER}/.cache/yarn" && \ + fix-permissions "${CONDA_DIR}" && \ + fix-permissions "/home/${NB_USER}" + +ENV JUPYTER_PORT=8888 +EXPOSE $JUPYTER_PORT + +# Configure container startup +CMD ["start-notebook.py"] + +# Copy local files as late as possible to avoid cache busting +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/ + +# 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 +HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 \ + CMD /etc/jupyter/docker_healthcheck.py || exit 1 + +# Switch back to jovyan to avoid accidental container runs as root +USER ${NB_UID} + +WORKDIR "${HOME}" diff --git a/Dockerfile.base-notebook b/spawnerdockerfile/Dockerfile.base-notebook similarity index 100% rename from Dockerfile.base-notebook rename to spawnerdockerfile/Dockerfile.base-notebook diff --git a/spawnerdockerfile/README.md b/spawnerdockerfile/README.md new file mode 100644 index 0000000..5f61f9d --- /dev/null +++ b/spawnerdockerfile/README.md @@ -0,0 +1,23 @@ +# Base Jupyter Notebook Stack + +> **Images hosted on Docker Hub are no longer updated. Please, use [quay.io image](https://quay.io/repository/jupyter/base-notebook)** + +[![docker pulls](https://img.shields.io/docker/pulls/jupyter/base-notebook.svg)](https://hub.docker.com/r/jupyter/base-notebook/) +[![docker stars](https://img.shields.io/docker/stars/jupyter/base-notebook.svg)](https://hub.docker.com/r/jupyter/base-notebook/) +[![image size](https://img.shields.io/docker/image-size/jupyter/base-notebook/latest)](https://hub.docker.com/r/jupyter/base-notebook/ "jupyter/base-notebook image size") + +GitHub Actions in the project builds and pushes this image to the Registry. + +Please visit the project documentation site for help to use and contribute to this image and others. + +- [Jupyter Docker Stacks on ReadTheDocs](https://jupyter-docker-stacks.readthedocs.io/en/latest/index.html) +- [Selecting an Image :: Core Stacks :: jupyter/base-notebook](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#jupyter-base-notebook) + +# 构建docker-compose spawner镜像的Dockerfile + +构建基[础镜像参考](https://github.com/jupyter/docker-stacks) + +```shell +cp docker-stacks/images/base-notebook/* ./spawnerdockerfile/ +docker buildx build -t hotwa/notebook:latest . -f Dockerfile.base-notebook --load +``` \ No newline at end of file diff --git a/spawnerdockerfile/docker_healthcheck.py b/spawnerdockerfile/docker_healthcheck.py new file mode 100755 index 0000000..8f3338e --- /dev/null +++ b/spawnerdockerfile/docker_healthcheck.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +import json +import os +from pathlib import Path + +import requests + +# Several operations below deliberately don't check for possible errors +# As this is a healthcheck, it should succeed or raise an exception on error + +runtime_dir = Path("/home/") / os.environ["NB_USER"] / ".local/share/jupyter/runtime/" +json_file = next(runtime_dir.glob("*server-*.json")) + +url = json.loads(json_file.read_bytes())["url"] +url = url + "api" + +proxies = { + "http": "", + "https": "", +} + +r = requests.get(url, proxies=proxies, verify=False) # request without SSL verification +r.raise_for_status() +print(r.content) diff --git a/spawnerdockerfile/jupyter_server_config.py b/spawnerdockerfile/jupyter_server_config.py new file mode 100644 index 0000000..c0cca3a --- /dev/null +++ b/spawnerdockerfile/jupyter_server_config.py @@ -0,0 +1,58 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +# mypy: ignore-errors +import os +import stat +import subprocess +from pathlib import Path + +from jupyter_core.paths import jupyter_data_dir + +c = get_config() # noqa: F821 +c.ServerApp.ip = "0.0.0.0" +c.ServerApp.open_browser = False + +# to output both image/svg+xml and application/pdf plot formats in the notebook file +c.InlineBackend.figure_formats = {"png", "jpeg", "svg", "pdf"} + +# https://github.com/jupyter/notebook/issues/3130 +c.FileContentsManager.delete_to_trash = False + +# Generate a self-signed certificate +OPENSSL_CONFIG = """\ +[req] +distinguished_name = req_distinguished_name +[req_distinguished_name] +""" +if "GEN_CERT" in os.environ: + dir_name = Path(jupyter_data_dir()) + dir_name.mkdir(parents=True, exist_ok=True) + pem_file = dir_name / "notebook.pem" + + # Generate an openssl.cnf file to set the distinguished name + cnf_file = Path(os.getenv("CONDA_DIR", "/usr/lib")) / "ssl/openssl.cnf" + if not cnf_file.exists(): + cnf_file.write_text(OPENSSL_CONFIG) + + # Generate a certificate if one doesn't exist on a disk + subprocess.check_call( + [ + "openssl", + "req", + "-new", + "-newkey=rsa:2048", + "-days=365", + "-nodes", + "-x509", + "-subj=/C=XX/ST=XX/L=XX/O=generated/CN=generated", + f"-keyout={pem_file}", + f"-out={pem_file}", + ] + ) + # Restrict access to the file + pem_file.chmod(stat.S_IRUSR | stat.S_IWUSR) + c.ServerApp.certfile = str(pem_file) + +# Change default umask for all subprocesses of the Server if set in the environment +if "NB_UMASK" in os.environ: + os.umask(int(os.environ["NB_UMASK"], 8)) diff --git a/spawnerdockerfile/start-notebook.py b/spawnerdockerfile/start-notebook.py new file mode 100755 index 0000000..b99ff31 --- /dev/null +++ b/spawnerdockerfile/start-notebook.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +import os +import shlex +import sys + +# 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.py instead of start-notebook.py to start a server associated with JupyterHub." + ) + command = ["/usr/local/bin/start-singleuser.py"] + sys.argv[1:] + os.execvp(command[0], command) + + +# Wrap everything in start.sh, no matter what +command = ["/usr/local/bin/start.sh"] + +# 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 +if "NOTEBOOK_ARGS" in os.environ: + command += shlex.split(os.environ["NOTEBOOK_ARGS"]) + +# Pass through any other args we were passed on the command line +command += sys.argv[1:] + +# Execute the command! +os.execvp(command[0], command) diff --git a/spawnerdockerfile/start-notebook.sh b/spawnerdockerfile/start-notebook.sh new file mode 100755 index 0000000..c47ebba --- /dev/null +++ b/spawnerdockerfile/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/spawnerdockerfile/start-singleuser.py b/spawnerdockerfile/start-singleuser.py new file mode 100755 index 0000000..2dcf6c0 --- /dev/null +++ b/spawnerdockerfile/start-singleuser.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# 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", "jupyterhub-singleuser"] + +# 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"]) + +# Pass any other args we have been passed through +command += sys.argv[1:] + +# Execute the command! +os.execvp(command[0], command) diff --git a/spawnerdockerfile/start-singleuser.sh b/spawnerdockerfile/start-singleuser.sh new file mode 100755 index 0000000..ecf0e06 --- /dev/null +++ b/spawnerdockerfile/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 "$@"