diff --git a/tljh-plasma/tljh_plasma/__init__.py b/tljh-plasma/tljh_plasma/__init__.py index 106a0c9..681edb6 100644 --- a/tljh-plasma/tljh_plasma/__init__.py +++ b/tljh-plasma/tljh_plasma/__init__.py @@ -117,6 +117,7 @@ def tljh_custom_jupyterhub_config(c, tljh_config_file=CONFIG_FILE): # increase the timeout to be able to pull larger Docker images c.PlasmaSpawner.start_timeout = 120 c.PlasmaSpawner.pull_policy = "Never" + # TODO: re-enable c.PlasmaSpawner.remove = False c.PlasmaSpawner.default_url = "/lab" # TODO: change back to jupyterhub-singleuser diff --git a/tljh-plasma/tljh_plasma/entrypoint/entrypoint.sh b/tljh-plasma/tljh_plasma/entrypoint/entrypoint.sh index e9a66d8..0a91c1c 100755 --- a/tljh-plasma/tljh_plasma/entrypoint/entrypoint.sh +++ b/tljh-plasma/tljh_plasma/entrypoint/entrypoint.sh @@ -43,4 +43,4 @@ export JUPYTER_PATH=${IMAGE_DIR}/.local/share/jupyter cd ${IMAGE_DIR} # execute the notebook process as the given user -exec su - $NB_USER -m -c '"$0" "$@"' -- "$@" +exec su $NB_USER -m -c '"$0" "$@"' -- "$@" diff --git a/tljh-plasma/tljh_plasma/entrypoint/repo2docker-entrypoint b/tljh-plasma/tljh_plasma/entrypoint/repo2docker-entrypoint deleted file mode 100755 index d541426..0000000 --- a/tljh-plasma/tljh_plasma/entrypoint/repo2docker-entrypoint +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/local/bin/python3-login -# note: must run on Python >= 3.5, which mainly means no f-strings - -# goals: -# - load environment variables from a login shell (bash -l) -# - preserve signal handling of subprocess (kill -TERM and friends) -# - tee output to a log file - -# Adapted from https://github.com/jupyterhub/repo2docker/blob/c6f97e55c19b44d6579d1d54087155f3e3df5338/repo2docker/buildpacks/repo2docker-entrypoint - -import fcntl -import os -import select -import signal -import shutil -import subprocess -import sys -import tempfile - -# output chunk size to read -CHUNK_SIZE = 1024 - -# signals to be forwarded to the child -# everything catchable, excluding SIGCHLD -SIGNALS = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD} - - -def main(): - - # open log file to send output to; - # preferred location of log file is: - # 1. REPO_DIR env variable - # 2. current working directory: "." - # 3. default temp directory for the OS (e.g. /tmp for linux) - log_dirs = [".", tempfile.gettempdir()] - log_file = None - if "REPO_DIR" in os.environ: - log_dirs.insert(0, os.environ["REPO_DIR"]) - for d in log_dirs: - log_path = os.path.join(d, ".jupyter-server-log.txt") - try: - log_file = open(log_path, "ab") - except Exception: - continue - else: - # success - break - # raise Exception if log_file could not be set - if log_file is None: - raise Exception("Could not open '.jupyter-server-log.txt' log file " ) - - # handle user override - NB_GID = os.environ.get("NB_UID") - PATH = os.environ.get("PATH").replace("jovyan", os.environ.get("NB_USER")) - IMAGE_DIR = os.path.join(os.environ.get("HOME"), os.environ.get("USER_IMAGE")) - - # add a new group for the user - subprocess.run(["groupadd", "-g", NB_GID, "-o", os.environ.get("NB_GROUP", os.environ.get("NB_USER"))]) - - # add the user and set their home directory - subprocess.run(["useradd", "--home", os.environ.get("HOME"), "-u", os.environ.get("NB_UID"), "-g", NB_GID, "-G", "100", "-l", os.environ.get("NB_USER")]) - - # copy the content from the default docker image to the user home directory - for file in os.listdir("/home/jovyan"): - if not os.path.exists(os.path.join(IMAGE_DIR, file)): - shutil.copytree(os.path.join("/home/jovyan", file), os.path.join(IMAGE_DIR, file)) - - # remove the .cache if it exists, as it can be a couple hundreds MB big - if os.path.exists(os.path.join(IMAGE_DIR, ".cache")): - shutil.rmtree(os.path.join(IMAGE_DIR, ".cache")) - - # set the name of the environment as the topbar text indicator - TOPBAR_TEXT_SETTINGS_DIR = os.path.join(IMAGE_DIR, ".jupyter/lab/user-settings/jupyterlab-topbar-text") - os.makedirs(TOPBAR_TEXT_SETTINGS_DIR, exist_ok=True) - with open(os.path.join(TOPBAR_TEXT_SETTINGS_DIR, "plugin.jupyterlab-settings"), "w") as f: - f.write("{\"editable\": false, \"text\":\"" + os.environ.get("USER_IMAGE") + "\"}") - - # set the correct permissions for the user home subdirectory - subprocess.run(["chown", "-R", os.environ.get("NB_USER") + ":" + os.environ.get("NB_USER"), IMAGE_DIR]) - - # set the Jupyter paths environment variables to find potential configuration - # and data files from the user environment base images home directories - os.environ["JUPYTER_CONFIG_DIR"] = os.path.join(IMAGE_DIR, ".jupyter") - os.environ["JUPYTER_PATH"] = os.path.join(IMAGE_DIR, ".local/share/jupyter") - - # start the notebook server from the environment directory - os.chdir(IMAGE_DIR) - - # # build the command - # # like `exec "$@"` - # command = sys.argv[1:] - # # load entrypoint override from env - # r2d_entrypoint = os.environ.get("R2D_ENTRYPOINT") - # if r2d_entrypoint: - # command.insert(0, r2d_entrypoint) - - command = ["su", "-", os.environ.get("NB_USER"), "-m", "-c", '"$0" "$@"', "--", "$@"] - - # launch the subprocess - child = subprocess.Popen( - command, - bufsize=1, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - - # hook up ~all signals so that every signal the parent gets, - # the children also get - - def relay_signal(sig, frame): - """Relay a signal to children""" - # DEBUG: show signal - child.send_signal(sig) - - for signum in SIGNALS: - signal.signal(signum, relay_signal) - - # tee output from child to both our stdout and the log file - def tee(chunk): - """Tee output from child to both our stdout and the log file""" - for f in [sys.stdout.buffer, log_file]: - f.write(chunk) - f.flush() - - # make stdout pipe non-blocking - # this means child.stdout.read(nbytes) - # will always return immediately, even if there's nothing to read - flags = fcntl.fcntl(child.stdout, fcntl.F_GETFL) - fcntl.fcntl(child.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) - poller = select.poll() - poller.register(child.stdout) - - # while child is running, constantly relay output - while child.poll() is None: - chunk = child.stdout.read(CHUNK_SIZE) - if chunk: - tee(chunk) - else: - # empty chunk means nothing to read - # wait for output on the pipe - # timeout is in milliseconds - poller.poll(1000) - - # child has exited, continue relaying any remaining output - # At this point, read() will return an empty string when it's done - chunk = child.stdout.read() - while chunk: - tee(chunk) - chunk = child.stdout.read() - - # make our returncode match the child's returncode - sys.exit(child.returncode) - - -if __name__ == "__main__": - main() \ No newline at end of file