mirror of
https://github.com/jupyterhub/the-littlest-jupyterhub.git
synced 2025-12-18 21:54:05 +08:00
Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e68745e38 | ||
|
|
a49f0a6889 | ||
|
|
bcfb426b5c | ||
|
|
59fb7fb8a2 | ||
|
|
95b54414ca | ||
|
|
eb5c0e4758 | ||
|
|
92e317d41f | ||
|
|
812be54635 | ||
|
|
3ddc1e8d59 | ||
|
|
a059d6ffbb | ||
|
|
856c647e88 | ||
|
|
a89021c49e | ||
|
|
a98299c629 | ||
|
|
e8602fc4a1 | ||
|
|
0de2db365a | ||
|
|
436c81f26d | ||
|
|
363dc45b52 | ||
|
|
1a9f3d65a4 | ||
|
|
3f55ac2912 | ||
|
|
92465ade36 | ||
|
|
2282e61894 | ||
|
|
94a16d6ddd | ||
|
|
7be7eb4968 | ||
|
|
0c322774e2 | ||
|
|
13c8946d8c | ||
|
|
f7118edf48 | ||
|
|
b034fc6713 | ||
|
|
3321a50f31 | ||
|
|
73f8833f0a | ||
|
|
5834f14b83 | ||
|
|
9111b73cee | ||
|
|
3bd739e8dc | ||
|
|
7a491e403c | ||
|
|
50c1b5f894 | ||
|
|
5c8cb5bb26 | ||
|
|
8490ef2949 | ||
|
|
5172ceb4f9 | ||
|
|
634956c33e | ||
|
|
5278351eb1 | ||
|
|
22e08a458b | ||
|
|
dcbb37688e | ||
|
|
c492c176bc | ||
|
|
65c5d78ea5 | ||
|
|
5abf657bdf | ||
|
|
c49fada1c8 | ||
|
|
708bbec2a4 | ||
|
|
729a3ceb28 | ||
|
|
9c7427923d | ||
|
|
bcc64e0d8b | ||
|
|
a5f966927f | ||
|
|
d0fb92eda5 | ||
|
|
52478abb65 | ||
|
|
96ac0d3538 | ||
|
|
13ed32b499 | ||
|
|
bdbb3adbbc | ||
|
|
2b29bd9f0a | ||
|
|
8fd41cc77a | ||
|
|
7e39e993da | ||
|
|
5469e21e74 | ||
|
|
38a01e8406 | ||
|
|
9bcfa70326 | ||
|
|
7474b876f1 | ||
|
|
5ae31ce169 | ||
|
|
b94a281ff8 | ||
|
|
67dd3c8abe | ||
|
|
c578a7bec0 | ||
|
|
51f8470535 | ||
|
|
196208ae58 | ||
|
|
242dca4376 | ||
|
|
5169301a0a | ||
|
|
4ddd798928 | ||
|
|
f921acc183 | ||
|
|
46e4045568 | ||
|
|
743f729902 | ||
|
|
48fe440372 | ||
|
|
8348d4ad96 | ||
|
|
579b7eb5ba | ||
|
|
0248785aed | ||
|
|
beed70060c | ||
|
|
d9a0a5fc7a | ||
|
|
4e397bc687 | ||
|
|
7d8a84860f | ||
|
|
2fe9912333 | ||
|
|
02c3d7539b | ||
|
|
10ba571bde | ||
|
|
7fa4e2bcec | ||
|
|
2faf0d3a5f | ||
|
|
b35851f2b7 | ||
|
|
9755938d7d | ||
|
|
1f7d6d1c55 | ||
|
|
9060267458 | ||
|
|
4912cffe65 | ||
|
|
d0c9aa263a | ||
|
|
fa363658df | ||
|
|
166eba6735 | ||
|
|
5a0de137d2 | ||
|
|
929536de7b | ||
|
|
ef5c6c56b7 | ||
|
|
fb01dea5e4 | ||
|
|
78d4b7fbc4 | ||
|
|
d292457803 | ||
|
|
6dd2ee812a | ||
|
|
641a02ed07 | ||
|
|
1c9301e4d7 | ||
|
|
723c4e756d | ||
|
|
eeae5f06be | ||
|
|
4abf3f003f | ||
|
|
3da9743640 | ||
|
|
aec331b114 | ||
|
|
0f5a0ccbe0 | ||
|
|
f1c7e2d90f | ||
|
|
5cb6b69020 | ||
|
|
7faf997c7b | ||
|
|
79134e3a2f | ||
|
|
e869633871 | ||
|
|
793f6237b4 | ||
|
|
caab46e08a | ||
|
|
dcb91d4e6a | ||
|
|
ed6ff645c2 | ||
|
|
291c096a17 | ||
|
|
eafc10e82a | ||
|
|
58a679f584 | ||
|
|
9bde7e4680 | ||
|
|
6359bf498c | ||
|
|
44bdaf2fa4 | ||
|
|
1d1aefd518 | ||
|
|
542cf4de6b | ||
|
|
46b49c819c | ||
|
|
c7507fd799 | ||
|
|
fc8e19b1b5 | ||
|
|
1f21e3cb26 | ||
|
|
b13f09564d | ||
|
|
c40d95c02a | ||
|
|
c35a4cbe65 | ||
|
|
fb78464dec | ||
|
|
9efe59f4f7 | ||
|
|
e14b8d8b77 | ||
|
|
cc00bf3a1a | ||
|
|
c6d86d059f | ||
|
|
4cd5da40c3 | ||
|
|
55db9c5d0b | ||
|
|
e893959ce5 | ||
|
|
5fdc31f9a9 | ||
|
|
a8c6c40b2b | ||
|
|
854f5edd06 | ||
|
|
0122a21c71 | ||
|
|
e9de9f6042 | ||
|
|
2b1b6787b8 | ||
|
|
3a7c6f7b38 | ||
|
|
1a83791191 | ||
|
|
0f03f40eff | ||
|
|
6801c68333 | ||
|
|
2fbe803530 | ||
|
|
0da7d09298 | ||
|
|
91af54ade3 | ||
|
|
645251a134 | ||
|
|
dfeddc2e53 | ||
|
|
4b13303d01 | ||
|
|
ad1455e4da | ||
|
|
6f38ec6a95 | ||
|
|
b6ee97c770 | ||
|
|
bdfabc5ce8 | ||
|
|
86a4bb67c8 | ||
|
|
259d2ff11d | ||
|
|
3753a3c775 | ||
|
|
bc09121677 | ||
|
|
ff1f612d10 | ||
|
|
58181c9671 | ||
|
|
8d1033393c | ||
|
|
fc8f70463c |
12
.github/integration-test.py
vendored
12
.github/integration-test.py
vendored
@@ -10,7 +10,7 @@ GIT_REPO_PATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
|||||||
TEST_IMAGE_NAME = "test-systemd"
|
TEST_IMAGE_NAME = "test-systemd"
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache()
|
@functools.lru_cache
|
||||||
def _get_container_runtime_cli():
|
def _get_container_runtime_cli():
|
||||||
runtimes = ["docker", "podman"]
|
runtimes = ["docker", "podman"]
|
||||||
for runtime in runtimes:
|
for runtime in runtimes:
|
||||||
@@ -167,15 +167,23 @@ def run_test(
|
|||||||
command = f"python3 /srv/src/bootstrap/bootstrap.py --version={upgrade_from}"
|
command = f"python3 /srv/src/bootstrap/bootstrap.py --version={upgrade_from}"
|
||||||
run_command(container_name, command)
|
run_command(container_name, command)
|
||||||
|
|
||||||
|
# show user environment
|
||||||
|
command = "/opt/tljh/user/bin/mamba list"
|
||||||
|
run_command(container_name, command)
|
||||||
|
|
||||||
command = f"python3 /srv/src/bootstrap/bootstrap.py {' '.join(installer_args)}"
|
command = f"python3 /srv/src/bootstrap/bootstrap.py {' '.join(installer_args)}"
|
||||||
run_command(container_name, command)
|
run_command(container_name, command)
|
||||||
|
|
||||||
|
# show user environment (again if upgrade)
|
||||||
|
command = "/opt/tljh/user/bin/mamba list"
|
||||||
|
run_command(container_name, command)
|
||||||
|
|
||||||
# Install pkgs from requirements in hub's pip, where
|
# Install pkgs from requirements in hub's pip, where
|
||||||
# the bootstrap script installed the others
|
# the bootstrap script installed the others
|
||||||
command = "/opt/tljh/hub/bin/python3 -m pip install -r /srv/src/integration-tests/requirements.txt"
|
command = "/opt/tljh/hub/bin/python3 -m pip install -r /srv/src/integration-tests/requirements.txt"
|
||||||
run_command(container_name, command)
|
run_command(container_name, command)
|
||||||
|
|
||||||
# show environment
|
# show hub environment
|
||||||
command = "/opt/tljh/hub/bin/python3 -m pip freeze"
|
command = "/opt/tljh/hub/bin/python3 -m pip freeze"
|
||||||
run_command(container_name, command)
|
run_command(container_name, command)
|
||||||
|
|
||||||
|
|||||||
21
.github/workflows/integration-test.yaml
vendored
21
.github/workflows/integration-test.yaml
vendored
@@ -36,12 +36,15 @@ jobs:
|
|||||||
- name: "Debian 11, Py 3.9"
|
- name: "Debian 11, Py 3.9"
|
||||||
distro_image: "debian:11"
|
distro_image: "debian:11"
|
||||||
extra_flags: ""
|
extra_flags: ""
|
||||||
- name: "Ubuntu 20.04, Py 3.8"
|
- name: "Debian 12, Py 3.11"
|
||||||
distro_image: "ubuntu:20.04"
|
distro_image: "debian:12"
|
||||||
extra_flags: ""
|
extra_flags: ""
|
||||||
- name: "Ubuntu 22.04 Py 3.10"
|
- name: "Ubuntu 22.04 Py 3.10"
|
||||||
distro_image: "ubuntu:22.04"
|
distro_image: "ubuntu:22.04"
|
||||||
extra_flags: ""
|
extra_flags: ""
|
||||||
|
- name: "Ubuntu 24.04 Py 3.12"
|
||||||
|
distro_image: "ubuntu:24.04"
|
||||||
|
extra_flags: ""
|
||||||
- name: "Ubuntu 22.04, Py 3.10, from main"
|
- name: "Ubuntu 22.04, Py 3.10, from main"
|
||||||
distro_image: "ubuntu:22.04"
|
distro_image: "ubuntu:22.04"
|
||||||
extra_flags: --upgrade-from=main
|
extra_flags: --upgrade-from=main
|
||||||
@@ -51,10 +54,13 @@ jobs:
|
|||||||
- name: "Ubuntu 22.04, Py 3.10, from 0.2.0"
|
- name: "Ubuntu 22.04, Py 3.10, from 0.2.0"
|
||||||
distro_image: "ubuntu:22.04"
|
distro_image: "ubuntu:22.04"
|
||||||
extra_flags: --upgrade-from=0.2.0
|
extra_flags: --upgrade-from=0.2.0
|
||||||
|
- name: "Ubuntu 22.04, Py 3.10, from 1.*"
|
||||||
|
distro_image: "ubuntu:22.04"
|
||||||
|
extra_flags: --upgrade-from=1
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
@@ -116,10 +122,13 @@ jobs:
|
|||||||
distro_image: "ubuntu:22.04"
|
distro_image: "ubuntu:22.04"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
cache: pip
|
||||||
|
cache-dependency-path: |
|
||||||
|
integration-tests/requirements.txt
|
||||||
|
|
||||||
# FIXME: The test_bootstrap.py script has duplicated logic to run build
|
# FIXME: The test_bootstrap.py script has duplicated logic to run build
|
||||||
# and start images and run things in them. This makes tests slower,
|
# and start images and run things in them. This makes tests slower,
|
||||||
|
|||||||
27
.github/workflows/unit-test.yaml
vendored
27
.github/workflows/unit-test.yaml
vendored
@@ -46,40 +46,29 @@ jobs:
|
|||||||
- name: "Ubuntu 22.04, Py 3.10"
|
- name: "Ubuntu 22.04, Py 3.10"
|
||||||
ubuntu_version: "22.04"
|
ubuntu_version: "22.04"
|
||||||
python_version: "3.10"
|
python_version: "3.10"
|
||||||
|
- name: "Ubuntu 24.04, Py 3.12"
|
||||||
|
ubuntu_version: "24.04"
|
||||||
|
python_version: "3.12"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "${{ matrix.python_version }}"
|
python-version: "${{ matrix.python_version }}"
|
||||||
|
|
||||||
- name: Install venv, git and setup venv
|
- name: Install venv, git, pip and setup venv
|
||||||
run: |
|
run: |
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install --yes \
|
apt-get install --yes \
|
||||||
python3-venv \
|
python3-venv \
|
||||||
|
python3-pip \
|
||||||
bzip2 \
|
bzip2 \
|
||||||
git
|
git
|
||||||
|
|
||||||
python3 -m venv /srv/venv
|
python3 -m venv /srv/venv
|
||||||
echo '/srv/venv/bin' >> $GITHUB_PATH
|
echo '/srv/venv/bin' >> $GITHUB_PATH
|
||||||
|
|
||||||
# WARNING: This action loads a cache of pip dependencies based on the
|
|
||||||
# declared key, and it will save a cache for that key on job
|
|
||||||
# completion. Make sure to update the key to bust the cache
|
|
||||||
# properly if you make a change that should influence it.
|
|
||||||
- name: Load cached Python dependencies
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: /srv/venv/
|
|
||||||
key: >-
|
|
||||||
pip-
|
|
||||||
${{ matrix.runs_on }}-
|
|
||||||
${{ matrix.ubuntu_version }}-
|
|
||||||
${{ matrix.python_version }}-
|
|
||||||
${{ hashFiles('setup.py', 'dev-requirements.txt', '.github/workflows/unit-test.yaml') }}
|
|
||||||
|
|
||||||
- name: Install Python dependencies
|
- name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install -r dev-requirements.txt
|
pip install -r dev-requirements.txt
|
||||||
@@ -93,4 +82,4 @@ jobs:
|
|||||||
run: pytest tests
|
run: pytest tests
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
- uses: codecov/codecov-action@v3
|
- uses: codecov/codecov-action@v4
|
||||||
|
|||||||
@@ -11,18 +11,18 @@
|
|||||||
repos:
|
repos:
|
||||||
# Autoformat: Python code, syntax patterns are modernized
|
# Autoformat: Python code, syntax patterns are modernized
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.8.0
|
rev: v3.17.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args:
|
args:
|
||||||
- --py36-plus
|
- --py39-plus
|
||||||
# We need the bootstrap.py script to be parsable with Python 3.5, so we
|
# We need the bootstrap.py script to be parsable with Python 3.8, so we
|
||||||
# exclude it from the pyupgrade hook that will apply f-strings etc.
|
# exclude it from the pyupgrade hook that will apply f-strings etc.
|
||||||
exclude: bootstrap/bootstrap.py
|
exclude: bootstrap/bootstrap.py
|
||||||
|
|
||||||
# Autoformat: Python code
|
# Autoformat: Python code
|
||||||
- repo: https://github.com/PyCQA/autoflake
|
- repo: https://github.com/PyCQA/autoflake
|
||||||
rev: v2.2.0
|
rev: v2.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: autoflake
|
- id: autoflake
|
||||||
# args ref: https://github.com/PyCQA/autoflake#advanced-usage
|
# args ref: https://github.com/PyCQA/autoflake#advanced-usage
|
||||||
@@ -31,25 +31,25 @@ repos:
|
|||||||
|
|
||||||
# Autoformat: Python code
|
# Autoformat: Python code
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 5.12.0
|
rev: 5.13.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
|
|
||||||
# Autoformat: Python code
|
# Autoformat: Python code
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.3.0
|
rev: 24.8.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
# Autoformat: markdown, yaml
|
# Autoformat: markdown, yaml
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v3.0.0-alpha.9-for-vscode
|
rev: v4.0.0-alpha.8
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
|
|
||||||
# Misc...
|
# Misc...
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.6.0
|
||||||
# ref: https://github.com/pre-commit/pre-commit-hooks#hooks-available
|
# ref: https://github.com/pre-commit/pre-commit-hooks#hooks-available
|
||||||
hooks:
|
hooks:
|
||||||
# Autoformat: Makes sure files end in a newline and only a newline.
|
# Autoformat: Makes sure files end in a newline and only a newline.
|
||||||
@@ -64,7 +64,7 @@ repos:
|
|||||||
|
|
||||||
# Lint: Python code
|
# Lint: Python code
|
||||||
- repo: https://github.com/pycqa/flake8
|
- repo: https://github.com/pycqa/flake8
|
||||||
rev: "6.0.0"
|
rev: "7.1.1"
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ This script is run as:
|
|||||||
|
|
||||||
Constraints:
|
Constraints:
|
||||||
|
|
||||||
- The entire script should be compatible with Python 3.8, which is the default on
|
- The entire script should be compatible with Python 3.9, which is the default on
|
||||||
Ubuntu 20.04.
|
Debian 11.
|
||||||
- The script should parse in Python 3.6 as we print error messages for using
|
- The script should parse in Python 3.8 as we print error messages for using
|
||||||
Ubuntu 18.04 which comes with Python 3.6 by default.
|
Ubuntu 20.04 which comes with Python 3.8 by default.
|
||||||
- The script must depend only on stdlib modules, as no previous installation
|
- The script must depend only on stdlib modules, as no previous installation
|
||||||
of dependencies can be assumed.
|
of dependencies can be assumed.
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ Command line flags, from "bootstrap.py --help":
|
|||||||
can also pass a branch name such as 'main' or a
|
can also pass a branch name such as 'main' or a
|
||||||
commit hash.
|
commit hash.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
@@ -209,22 +210,22 @@ def ensure_host_system_can_install_tljh():
|
|||||||
Check if TLJH is installable in current host system and exit with a clear
|
Check if TLJH is installable in current host system and exit with a clear
|
||||||
error message otherwise.
|
error message otherwise.
|
||||||
"""
|
"""
|
||||||
# Require Ubuntu 20.04+ or Debian 11+
|
# Require Ubuntu 22.04+ or Debian 11+
|
||||||
distro = get_os_release_variable("ID")
|
distro = get_os_release_variable("ID")
|
||||||
version = get_os_release_variable("VERSION_ID")
|
version = get_os_release_variable("VERSION_ID")
|
||||||
if distro not in ["ubuntu", "debian"]:
|
if distro not in ["ubuntu", "debian"]:
|
||||||
print("The Littlest JupyterHub currently supports Ubuntu or Debian Linux only")
|
print("The Littlest JupyterHub currently supports Ubuntu or Debian Linux only")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif distro == "ubuntu" and _parse_version(version) < (20, 4):
|
elif distro == "ubuntu" and _parse_version(version) < (22, 4):
|
||||||
print("The Littlest JupyterHub requires Ubuntu 20.04 or higher")
|
print("The Littlest JupyterHub requires Ubuntu 22.04 or higher")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif distro == "debian" and _parse_version(version) < (11,):
|
elif distro == "debian" and _parse_version(version) < (11,):
|
||||||
print("The Littlest JupyterHub requires Debian 11 or higher")
|
print("The Littlest JupyterHub requires Debian 11 or higher")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Require Python 3.8+
|
# Require Python 3.9+
|
||||||
if sys.version_info < (3, 8):
|
if sys.version_info < (3, 9):
|
||||||
print(f"bootstrap.py must be run with at least Python 3.8, found {sys.version}")
|
print(f"bootstrap.py must be run with at least Python 3.9, found {sys.version}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Require systemd (systemctl is a part of systemd)
|
# Require systemd (systemctl is a part of systemd)
|
||||||
|
|||||||
27
docs/conf.py
27
docs/conf.py
@@ -20,6 +20,7 @@ author = "Project Jupyter Contributors"
|
|||||||
#
|
#
|
||||||
extensions = [
|
extensions = [
|
||||||
"sphinx_copybutton",
|
"sphinx_copybutton",
|
||||||
|
"sphinx.ext.intersphinx",
|
||||||
"sphinxext.opengraph",
|
"sphinxext.opengraph",
|
||||||
"sphinxext.rediraffe",
|
"sphinxext.rediraffe",
|
||||||
"myst_parser",
|
"myst_parser",
|
||||||
@@ -74,6 +75,32 @@ myst_enable_extensions = [
|
|||||||
"fieldlist",
|
"fieldlist",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for intersphinx extension ---------------------------------------
|
||||||
|
# ref: https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration
|
||||||
|
#
|
||||||
|
# The extension makes us able to link like to other projects like below.
|
||||||
|
#
|
||||||
|
# rST - :external:py:class:`jupyterhub.spawner.Spawner`
|
||||||
|
# MyST - {external:py:class}`jupyterhub.spawner.Spawner`
|
||||||
|
#
|
||||||
|
# rST - :external:py:attribute:`jupyterhub.spawner.Spawner.default_url`
|
||||||
|
# MyST - {external:py:attribute}`jupyterhub.spawner.Spawner.default_url`
|
||||||
|
#
|
||||||
|
# To see what we can link to, do the following where "objects.inv" is appended
|
||||||
|
# to the sphinx based website:
|
||||||
|
#
|
||||||
|
# python -m sphinx.ext.intersphinx https://jupyterhub.readthedocs.io/en/stable/objects.inv
|
||||||
|
#
|
||||||
|
intersphinx_mapping = {
|
||||||
|
"jupyterhub": ("https://jupyterhub.readthedocs.io/en/stable/", None),
|
||||||
|
}
|
||||||
|
|
||||||
|
# intersphinx_disabled_reftypes set based on recommendation in
|
||||||
|
# https://docs.readthedocs.io/en/stable/guides/intersphinx.html#using-intersphinx
|
||||||
|
intersphinx_disabled_reftypes = ["*"]
|
||||||
|
|
||||||
|
|
||||||
# -- Options for linkcheck builder -------------------------------------------
|
# -- Options for linkcheck builder -------------------------------------------
|
||||||
# ref: https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder
|
# ref: https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ If so, you can tell your deployment to use these files:
|
|||||||
sudo tljh-config set https.enabled true
|
sudo tljh-config set https.enabled true
|
||||||
sudo tljh-config set https.tls.key /etc/mycerts/mydomain.key
|
sudo tljh-config set https.tls.key /etc/mycerts/mydomain.key
|
||||||
sudo tljh-config set https.tls.cert /etc/mycerts/mydomain.cert
|
sudo tljh-config set https.tls.cert /etc/mycerts/mydomain.cert
|
||||||
|
sudo tljh-config add-item https.tls.domains yourhub.yourdomain.edu
|
||||||
```
|
```
|
||||||
|
|
||||||
Once you have loaded this, your config should look like:
|
Once you have loaded this, your config should look like:
|
||||||
@@ -103,6 +104,8 @@ https:
|
|||||||
tls:
|
tls:
|
||||||
key: /etc/mycerts/mydomain.key
|
key: /etc/mycerts/mydomain.key
|
||||||
cert: /etc/mycerts/mydomain.cert
|
cert: /etc/mycerts/mydomain.cert
|
||||||
|
domains:
|
||||||
|
- yourhub.yourdomain.edu
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, you can reload the proxy to load the new configuration:
|
Finally, you can reload the proxy to load the new configuration:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ PrivateDevices=yes
|
|||||||
ProtectKernelTunables=yes
|
ProtectKernelTunables=yes
|
||||||
ProtectKernelModules=yes
|
ProtectKernelModules=yes
|
||||||
Environment=TLJH_INSTALL_PREFIX=/opt/tljh
|
Environment=TLJH_INSTALL_PREFIX=/opt/tljh
|
||||||
ExecStart=/opt/tljh/hub/bin/python3 -m jupyterhub.app -f jupyterhub_config.py --upgrade-db
|
ExecStart=/opt/tljh/hub/bin/python3 -m jupyterhub -f jupyterhub_config.py --upgrade-db
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
64
docs/howto/admin/upgrade-tljh.md
Normal file
64
docs/howto/admin/upgrade-tljh.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
(howto-admin-upgrade-tljh)=
|
||||||
|
|
||||||
|
# Upgrade TLJH
|
||||||
|
|
||||||
|
A TLJH installation is supposed to be upgradable to get updates to JupyterHub
|
||||||
|
itself and its dependencies in the [hub environment](hub-environment). For
|
||||||
|
details on what is done during an upgrade, see
|
||||||
|
[](topic-installer-upgrade-actions).
|
||||||
|
|
||||||
|
## Step 1: Read the changelog
|
||||||
|
|
||||||
|
Before making an upgrade, please read the [](changelog) to become aware about
|
||||||
|
breaking changes. If there are breaking changes, you may need to update your
|
||||||
|
configuration files or take other actions as well as part of the upgrade.
|
||||||
|
|
||||||
|
Adjusting to the breaking changes isn't part of this documentation, please rely
|
||||||
|
on the TLJH changelog and the changelogs of related projects linked to from the
|
||||||
|
TLJH changelog.
|
||||||
|
|
||||||
|
## Step 2: Consider making a backup
|
||||||
|
|
||||||
|
Before making an upgrade, consider if you want to first make a backup in some
|
||||||
|
way. While upgrades between TLJH versions are tested with automation, there are
|
||||||
|
no guarantees.
|
||||||
|
|
||||||
|
This project does't yet provide documentation on how to make backups, but if
|
||||||
|
TLJH is installed on a virtual machine in a cloud, a good option is to try
|
||||||
|
create a snapshot of the associated disk. If this isn't an option, you could
|
||||||
|
consider making a backup of the files in `/opt/tljh` that contain most but not
|
||||||
|
all things during an upgrade, or perhaps only the JupyterHub database with
|
||||||
|
information about its users in `/opt/tljh/state` together with some other
|
||||||
|
details.
|
||||||
|
|
||||||
|
## Step 3: Make the upgrade
|
||||||
|
|
||||||
|
To initialize the upgrade, do the following from a terminal on the machine where
|
||||||
|
TLJH is installed.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# IMPORTANT: This should NOT be run from a JupyterHub started user server, but
|
||||||
|
# should only run from a standalone terminal session in the machine
|
||||||
|
# where TLJH has been installed.
|
||||||
|
#
|
||||||
|
curl -L https://tljh.jupyter.org/bootstrap.py \
|
||||||
|
| sudo python3 - \
|
||||||
|
--version=latest
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also upgrade to specific version by changing `--version=latest` to
|
||||||
|
`--version=1.0.0` or similar. There is no need to specify admin users etc again.
|
||||||
|
|
||||||
|
## Step 4: Verify function
|
||||||
|
|
||||||
|
After having made an upgrade its always good to verify that the JupyterHub
|
||||||
|
installation still works as expected. You may want to try logging out, logging
|
||||||
|
in, and starting a new server for example.
|
||||||
|
|
||||||
|
If you have issues consider the [](troubleshooting) documentation. If you need
|
||||||
|
help you can ask questions in [Jupyter forum], and if you think there is a bug
|
||||||
|
or documentation improvement that should be made you can open an issue or pull
|
||||||
|
request in the [TLJH GitHub project].
|
||||||
|
|
||||||
|
[jupyter forum]: https://discourse.jupyter.org/c/jupyterhub/tljh
|
||||||
|
[tljh github project]: https://github.com/jupyterhub/the-littlest-jupyterhub
|
||||||
@@ -149,9 +149,10 @@ If it is not provided as array, there is an easy fix. Just add these lines to
|
|||||||
your `awscognito.py`:
|
your `awscognito.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def claim_groups_key_func(user_data_resp_json):
|
def groups_key_func(auth_state):
|
||||||
return [user_data_resp_json['custom:department']]
|
return [auth_state['oauth_user']['custom:department']]
|
||||||
|
|
||||||
c.GenericOAuthenticator.claim_groups_key = claim_groups_key_func
|
c.GenericOAuthenticator.manage_groups = True
|
||||||
|
c.GenericOAuthenticator.auth_state_groups_key = groups_key_func
|
||||||
c.GenericOAuthenticator.allowed_groups = ["AA BB CC", "AA BB DD"]
|
c.GenericOAuthenticator.allowed_groups = ["AA BB CC", "AA BB DD"]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -91,10 +91,16 @@ For more information on `tljh-config`, see [](/topic/tljh-config).
|
|||||||
4. Tell your JupyterHub to _use_ the GitHub OAuthenticator for authentication:
|
4. Tell your JupyterHub to _use_ the GitHub OAuthenticator for authentication:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo tljh-config set auth.type oauthenticator.github.GitHubOAuthenticator
|
sudo tljh-config set auth.type github
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Restart your JupyterHub so that new users see these changes:
|
5. Tell JupyterHub which users to allow, if you haven't already:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo tljh-config add-item users.allowed good-user_1
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Restart your JupyterHub so that new users see these changes:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo tljh-config reload
|
sudo tljh-config reload
|
||||||
|
|||||||
@@ -42,4 +42,4 @@ tljh-config reload
|
|||||||
|
|
||||||
## Optional features
|
## Optional features
|
||||||
|
|
||||||
More optional features are available on the `authenticator documentation <https://native-authenticator.readthedocs.io/en/latest/>`
|
More optional features are available on the [authenticator documentation](https://native-authenticator.readthedocs.io/en/latest/)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ content/share-data
|
|||||||
user-env/user-environment
|
user-env/user-environment
|
||||||
user-env/notebook-interfaces
|
user-env/notebook-interfaces
|
||||||
user-env/server-resources
|
user-env/server-resources
|
||||||
|
user-env/override-lab-settings
|
||||||
```
|
```
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
@@ -55,6 +56,7 @@ admin/nbresuse
|
|||||||
admin/https
|
admin/https
|
||||||
admin/enable-extensions
|
admin/enable-extensions
|
||||||
admin/systemd
|
admin/systemd
|
||||||
|
admin/upgrade-tljh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cloud provider configuration
|
## Cloud provider configuration
|
||||||
|
|||||||
@@ -1,49 +1,47 @@
|
|||||||
(howto/user-env/notebook-interfaces)=
|
(howto/user-env/notebook-interfaces)=
|
||||||
|
|
||||||
# Change default User Interface
|
# Change default user interface
|
||||||
|
|
||||||
By default, logging into TLJH puts you in the classic Jupyter Notebook
|
By default a user starting a server will see the JupyterLab interface. This can
|
||||||
interface we all know and love. However, there are at least two other
|
be changed with TLJH config `user_environment.default_app` or with the
|
||||||
popular notebook interfaces you can use:
|
JupyterHub config
|
||||||
|
{external:py:attr}`jupyterhub.spawner.Spawner.default_url` directly.
|
||||||
|
|
||||||
1. [JupyterLab](http://jupyterlab.readthedocs.io/en/stable/)
|
The TLJH config supports the options `jupyterlab` and `classic`, which
|
||||||
2. [nteract](https://nteract.io/)
|
translates to a `Spawner.default_url` config of `/lab` and `/tree`.
|
||||||
|
|
||||||
Both these interfaces are also shipped with TLJH by default. You can try
|
Both these interfaces are also shipped with TLJH by default. You can try them
|
||||||
them temporarily, or set them to be the default interface whenever you
|
temporarily, or set them to be the default interface whenever you login.
|
||||||
login.
|
|
||||||
|
|
||||||
## Trying an alternate interface temporarily
|
## Trying an alternate interface temporarily
|
||||||
|
|
||||||
When you log in & start your server, by default the URL in your browser
|
When you log in and start your server, by default the URL in your browser will
|
||||||
will be something like `/user/<username>/tree`. The `/tree` is what
|
be something like `/user/<username>/lab`. The `/lab` is what tells the jupyter
|
||||||
tells the notebook server to give you the classic notebook interface.
|
server to give you the JupyterLab user interface.
|
||||||
|
|
||||||
- **For the JupyterLab interface**: change `/tree` to `/lab`.
|
As an example, you can update the URL to not end with `/lab`, but instead end
|
||||||
- **For the nteract interface**: change `/tree` to `/nteract`
|
with `/tree` to temporarily switch to the classic interface.
|
||||||
|
|
||||||
You can play around with them and see what fits your use cases best.
|
## Changing the default user interface using TLJH config
|
||||||
|
|
||||||
## Changing the default user interface
|
You can change the default url, and therefore the interface users get when they
|
||||||
|
log in by modifying TLJH config as an admin user.
|
||||||
|
|
||||||
You can change the default interface users get when they log in by
|
1. To launch the classic notebook interface when users log in, run the
|
||||||
modifying `config.yaml` as an admin user.
|
following in the admin console:
|
||||||
|
|
||||||
1. To launch **JupyterLab** when users log in, run the following in an
|
```bash
|
||||||
admin console:
|
sudo tljh-config set user_environment.default_app classic
|
||||||
|
```
|
||||||
|
|
||||||
|
1. To launch JupyterLab when users log in, run the following in an admin
|
||||||
|
console:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo tljh-config set user_environment.default_app jupyterlab
|
sudo tljh-config set user_environment.default_app jupyterlab
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Alternatively, to launch **nteract** when users log in, run the
|
1. Apply the changes by restarting JupyterHub. This should not disrupt
|
||||||
following in the admin console:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo tljh-config set user_environment.default_app nteract
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Apply the changes by restarting JupyterHub. This should not disrupt
|
|
||||||
current users.
|
current users.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
121
docs/howto/user-env/override-lab-settings.md
Normal file
121
docs/howto/user-env/override-lab-settings.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
(topic-override-lab-settings)=
|
||||||
|
|
||||||
|
# Setting New Default JupyterLab Settings
|
||||||
|
|
||||||
|
If you or other users of your hub tend to use JupyterLab as your default notebook app,
|
||||||
|
then you may want to override some of the default settings for the users of your hub.
|
||||||
|
You can do this by creating a file `/opt/tljh/user/share/jupyter/lab/settings/overrides.json`
|
||||||
|
with the necessary settings.
|
||||||
|
|
||||||
|
This how-to guide will go through the necessary steps to set new defaults
|
||||||
|
for all users of your `TLJH` by example: setting the default theme to **JupyterLab Dark**.
|
||||||
|
|
||||||
|
## Step 1: Change your Personal Settings
|
||||||
|
|
||||||
|
The easiest way to set new default settings for all users starts with
|
||||||
|
configuring your own settings preferences to what you would like everyone else to have.
|
||||||
|
|
||||||
|
1. Make sure you are in the [JupyterLab notebook interface](#howto/user-env/notebook-interfaces),
|
||||||
|
which will look something like `http(s)://<YOUR-HUB-IP>/user/<YOUR_USERNAME/lab`.
|
||||||
|
|
||||||
|
1. Go to **Settings** in the menu bar and select **Theme -> JupyterLab Dark**.
|
||||||
|
|
||||||
|
## Step 2: Determine your Personal Settings Configuration
|
||||||
|
|
||||||
|
To set **JupyterLab Dark** as the default theme for all users, we will need to create
|
||||||
|
a `json` formatted file with the setting override. Now that you have changed your
|
||||||
|
personal setting, you can use the **JSON Settings Editor** to get the relevant
|
||||||
|
setting snippet to add to the `overrides.json` file later.
|
||||||
|
|
||||||
|
1. Go to **Settings -> Advanced Settings Editor** then select **JSON Settings Editor** on the right.
|
||||||
|
|
||||||
|
1. Scroll down and select **Theme**. You should see the `json` formatted configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
// Theme
|
||||||
|
// @jupyterlab/apputils-extension:themes
|
||||||
|
// Theme manager settings.
|
||||||
|
// *************************************
|
||||||
|
|
||||||
|
// Theme CSS Overrides
|
||||||
|
// Override theme CSS variables by setting key-value pairs here
|
||||||
|
"overrides": {
|
||||||
|
"code-font-family": null,
|
||||||
|
"code-font-size": null,
|
||||||
|
"content-font-family": null,
|
||||||
|
"content-font-size1": null,
|
||||||
|
"ui-font-family": null,
|
||||||
|
"ui-font-size1": null
|
||||||
|
},
|
||||||
|
|
||||||
|
// Selected Theme
|
||||||
|
// Application-level visual styling theme
|
||||||
|
"theme": "JupyterLab Dark",
|
||||||
|
|
||||||
|
// Scrollbar Theming
|
||||||
|
// Enable/disable styling of the application scrollbars
|
||||||
|
"theme-scrollbars": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Determine the setting that you want to change. In this example it's the `theme`
|
||||||
|
setting of `@jupyterlab/apputils-extension:theme` as can be seen above.
|
||||||
|
|
||||||
|
1. Build your `json` snippet. In this case, our snippet should look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"@jupyterlab/apputils-extension:themes": {
|
||||||
|
"theme": "JupyterLab Dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We only want to change the **Selected Theme**, so we don't need to include
|
||||||
|
the other theme-related settings for CSS and the scrollbar.
|
||||||
|
|
||||||
|
:::{note}
|
||||||
|
To apply overrides for more than one setting, separate each setting by commas. For example,
|
||||||
|
if you _also_ wanted to change the interval at which the notebook autosaves your content, you can use
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"@jupyterlab/apputils-extension:themes": {
|
||||||
|
"theme": "JupyterLab Dark"
|
||||||
|
},
|
||||||
|
|
||||||
|
"@jupyterlab/docmanager-extension:plugin": {
|
||||||
|
"autosaveInterval": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Step 3: Apply the Overrides to the Hub
|
||||||
|
|
||||||
|
Once you have your setting snippet created, you can add it to the `overrides.json` file
|
||||||
|
so that it gets applied to all users.
|
||||||
|
|
||||||
|
1. First, create the settings directory if it doesn't already exist:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /opt/tljh/user/share/jupyter/lab/settings
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Use `nano` to create and add content to the `overrides.json` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /opt/tljh/user/share/jupyter/lab/settings/overrides.json
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Copy and paste your snippet into the file and save.
|
||||||
|
|
||||||
|
1. Reload your configuration:
|
||||||
|
```bash
|
||||||
|
sudo tljh-config reload
|
||||||
|
```
|
||||||
|
|
||||||
|
The new default settings should now be set for all users in your `TLJH` using the
|
||||||
|
JupyterLab notebook interface.
|
||||||
BIN
docs/images/control-panel-menu.png
Normal file
BIN
docs/images/control-panel-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -1,11 +1,10 @@
|
|||||||
Most administration & configuration of the JupyterHub can be done from the
|
Most administration & configuration of the JupyterHub can be done from the
|
||||||
web UI directly. Let's add a few users who can log in!
|
web UI directly. Let's add a few users who can log in!
|
||||||
|
|
||||||
1. Open the **Control Panel** by clicking the control panel button on the top
|
1. In the File menu select the entry for the **Hub Control Panel**.
|
||||||
right of your JupyterHub.
|
|
||||||
|
|
||||||
```{image} ../images/control-panel-button.png
|
```{image} ../images/control-panel-menu.png
|
||||||
:alt: Control panel button in notebook, top right
|
:alt: Hub Control panel entry in lab File menu
|
||||||
```
|
```
|
||||||
|
|
||||||
2. In the control panel, open the **Admin** link in the top left.
|
2. In the control panel, open the **Admin** link in the top left.
|
||||||
|
|||||||
@@ -139,10 +139,10 @@ Let's create the server on which we can run JupyterHub.
|
|||||||
SSH to connect (port 22).
|
SSH to connect (port 22).
|
||||||
|
|
||||||
If you have never used your Amazon account before, you'll have to select
|
If you have never used your Amazon account before, you'll have to select
|
||||||
**Create a new security group**. You should give it a disitnguishing name
|
**Create a new security group**. You should give it a distinctive name
|
||||||
under **Security group name**
|
under **Security group name**
|
||||||
such as `ssh_web` for future reference. If you have, one from before you can
|
such as `ssh_web` for future reference. If you already have a security group,
|
||||||
select it and adjust it to have the rules you need, if you prefer.
|
you can select it and adjust it to have the rules you need, if you prefer.
|
||||||
|
|
||||||
The rules will default to include `SSH`. Leave that there, and then click on
|
The rules will default to include `SSH`. Leave that there, and then click on
|
||||||
the **Add Rule** button. Under **Type** for the new rule, change the field
|
the **Add Rule** button. Under **Type** for the new rule, change the field
|
||||||
|
|||||||
@@ -1,24 +1,133 @@
|
|||||||
|
(changelog)=
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2.0
|
||||||
|
|
||||||
|
### 2.0.0b1 - 2024-09-30
|
||||||
|
|
||||||
|
This release bundles with the latest available software from the JupyterHub
|
||||||
|
ecosystem.
|
||||||
|
|
||||||
|
For instructions on how to make an upgrade, see [](howto-admin-upgrade-tljh).
|
||||||
|
|
||||||
|
#### Breaking changes
|
||||||
|
|
||||||
|
- JupyterHub 4.\* has been upgraded to >=5.1.0,<6
|
||||||
|
- Refer to the [JupyterHub changelog] for details and pay attention to the
|
||||||
|
entries for JupyterHub version 5.0.0.
|
||||||
|
- OAuthenticator 16.0.4 has been upgraded to >=17.0.0,<18
|
||||||
|
- If you are using an OAuthenticator based authenticator class
|
||||||
|
(GitHubOAuthenticator, GoogleOAuthenticator, ...), refer to the
|
||||||
|
[OAuthenticator changelog] for details and pay attention to the entries for
|
||||||
|
JupyterHub version 17.0.0.
|
||||||
|
- LDAPAuthenticator 1.3.2 has been upgraded to >=2.0.0,<3
|
||||||
|
- If you are using this authenticator class, refer to the [LDAPAuthenticator
|
||||||
|
changelog] for details and pay attention to the entries for
|
||||||
|
LDAPAuthenticator version 2.0.0.
|
||||||
|
- The configured JupyterHub Proxy class `traefik-proxy` and the `traefik` server
|
||||||
|
controlled by JupyterHub via the proxy class has been upgraded to a new major
|
||||||
|
version, but no breaking change are expected to be noticed for users.
|
||||||
|
|
||||||
|
[oauthenticator changelog]: https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html
|
||||||
|
[ldapauthenticator changelog]: https://github.com/jupyterhub/ldapauthenticator/blob/HEAD/CHANGELOG.md
|
||||||
|
|
||||||
|
#### Notable dependencies updated
|
||||||
|
|
||||||
|
A TLJH installation provides a Python environment where the software for
|
||||||
|
JupyterHub itself runs - _the hub environment_, and a Python environment where
|
||||||
|
the software of users runs - _the user environment_.
|
||||||
|
|
||||||
|
If you are installing TLJH for the first time, the user environment will be
|
||||||
|
setup initially with Python 3.12 and some other packages described in
|
||||||
|
[tljh/requirements-user-env-extras.txt].
|
||||||
|
|
||||||
|
If you are upgrading to this version of TLJH, the bare minimum is changed in the
|
||||||
|
user environment. The hub environment's dependencies are on the other hand
|
||||||
|
always upgraded to the latest version within the specified version range defined
|
||||||
|
in [tljh/requirements-hub-env.txt] and seen below.
|
||||||
|
|
||||||
|
[tljh/requirements-user-env-extras.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/2.0.0b1/tljh/requirements-user-env-extras.txt
|
||||||
|
[tljh/requirements-hub-env.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/2.0.0b1/tljh/requirements-hub-env.txt
|
||||||
|
|
||||||
|
The changes in the respective environments between TLJH version 1.0.0 and 2.0.0b1
|
||||||
|
are summarized below.
|
||||||
|
|
||||||
|
| Dependency changes in the _hub environment_ | Version in 1.0.0 | Version in 2.0.0b1 | Changelog link | Note |
|
||||||
|
| ------------------------------------------------------------------------------ | ---------------- | ------------------ | ---------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||||
|
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | >=4.0.2,<5 | >=5.1.0,<6 | [Changelog](https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html) | Running in the `jupyterhub` systemd service |
|
||||||
|
| [traefik](https://github.com/traefik/traefik) | 2.10.1 | 3.1.4 | [Changelog](https://github.com/traefik/traefik/blob/master/CHANGELOG.md) | Running in the `traefik` systemd service |
|
||||||
|
| [traefik-proxy](https://github.com/jupyterhub/traefik-proxy) | >=1.1.0,<2 | 2.\* | [Changelog](https://jupyterhub-traefik-proxy.readthedocs.io/en/latest/changelog.html) | Run by jupyterhub, controls `traefik` |
|
||||||
|
| [systemdspawner](https://github.com/jupyterhub/systemdspawner) | >=1.0.1,<2 | >=1.0.1,<2 | [Changelog](https://github.com/jupyterhub/systemdspawner/blob/master/CHANGELOG.md) | Run by jupyterhub, controls user servers via systemd |
|
||||||
|
| [jupyterhub-idle-culler](https://github.com/jupyterhub/jupyterhub-idle-culler) | >=1.2.1,<2 | >=1.4.0,<2 | [Changelog](https://github.com/jupyterhub/jupyterhub-idle-culler/blob/main/CHANGELOG.md) | Run by jupyterhub, stops inactivate servers etc. |
|
||||||
|
| [firstuseauthenticator](https://github.com/jupyterhub/firstuseauthenticator) | >=1.0.0,<2 | 1.1.0,<2 | [Changelog](https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html) | An optional way to authenticate users |
|
||||||
|
| [tmpauthenticator](https://github.com/jupyterhub/tmpauthenticator) | >=1.0.0,<2 | 1.0.0,<2 | [Changelog](https://github.com/jupyterhub/tmpauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||||
|
| [nativeauthenticator](https://github.com/jupyterhub/nativeauthenticator) | >=1.2.0,<2 | >=1.3.0,<2 | [Changelog](https://github.com/jupyterhub/nativeauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||||
|
| [oauthenticator](https://github.com/jupyterhub/oauthenticator) | >=16.0.4,<17 | >=17.0.0,<18 | [Changelog](https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html) | An optional way to authenticate users |
|
||||||
|
| [ldapauthenticator](https://github.com/jupyterhub/ldapauthenticator) | >=1.3.2,<2 | ==2.0.0b2 | [Changelog](https://github.com/jupyterhub/ldapauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||||
|
| [pip](https://github.com/pypa/pip) | >=23.1.2 | >=23.1.2 | [Changelog](https://pip.pypa.io/en/stable/news/) | - |
|
||||||
|
|
||||||
|
| Dependency changes in the _user environment_ | Version in 1.0.0 | Version in upgrade to 2.0.0b1 | Version in fresh install of 2.0.0b1 | Changelog link | Note |
|
||||||
|
| -------------------------------------------------------- | ---------------- | ----------------------------- | ----------------------------------- | --------------------------------------------------------------------------------- | ------------------------ |
|
||||||
|
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | >=4.0.2,<5 | >=5.1.0,<6 | >=5.1.0,<6 | [Changelog](https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html) | Always upgraded. |
|
||||||
|
| [pip](https://github.com/pypa/pip) | >=23.1.2 | >=23.1.2 | >=24.2 | [Changelog](https://pip.pypa.io/en/stable/news/) | Only upgraded if needed. |
|
||||||
|
| [conda](https://docs.conda.io/projects/conda/en/stable/) | >=4.10.0 | >=4.10.0 | ==24.7.1 | [Changelog](https://docs.conda.io/projects/conda/en/stable/release-notes.html) | Only upgraded if needed. |
|
||||||
|
| [mamba](https://mamba.readthedocs.io/en/latest/) | >=0.16.0 | >=0.16.0 | ==1.5.9 | [Changelog](https://github.com/mamba-org/mamba/blob/main/CHANGELOG.md) | Only upgraded if needed. |
|
||||||
|
|
||||||
|
#### New features added
|
||||||
|
|
||||||
|
- jupyterhub 5 [#989](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/989) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||||
|
- Validate tljh specific config [#962](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/962) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||||
|
- Add the ability to define conda channels in plugins via `tljh_extra_user_conda_channels` [#942](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/942) ([@yuvipanda](https://github.com/yuvipanda), [@consideRatio](https://github.com/consideRatio))
|
||||||
|
|
||||||
|
#### Bugs fixed
|
||||||
|
|
||||||
|
- fix `-m` invocation of jupyterhub [#988](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/988) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||||
|
- Re-install conda/mamba for every tljh upgrade (doesn't imply upgrade) [#968](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/968) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||||
|
- Add missing oauthenticator dependency for AzureADOAuthenticator [#959](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/959) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
|
||||||
|
#### Maintenance and upkeep improvements
|
||||||
|
|
||||||
|
- Bump the requirements-user-env-extras.txt lower version bounds [#1002](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1002) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- Update traefik from 2.10.1 to 3.1.4 [#1001](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1001) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- Update to install miniforge 24.7.1-2 from 24.7.1-0 [#999](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/999) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- Drop ubuntu 20.04, require py39, traefik-proxy v2, and ldapauthenticator v2 [#998](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/998) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||||
|
- consolidate lock file handling [#994](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/994) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio), [@jrdnbradford](https://github.com/jrdnbradford))
|
||||||
|
- update oauthenticator to 17 [#992](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/992) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||||
|
- Update base user environment to miniforge 24.7.1-0 (Python 3.12) [#990](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/990) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||||
|
- Add TLJH config lockfile [#976](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/976) ([@jrdnbradford](https://github.com/jrdnbradford), [@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||||
|
- tests: fix to catch test failure earlier when they really happen [#975](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/975) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||||
|
|
||||||
|
#### Documentation improvements
|
||||||
|
|
||||||
|
- Added missing details on how to add custom domain from manual HTTPS configuration [#983](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/983) ([@josedaudi](https://github.com/josedaudi), [@yuvipanda](https://github.com/yuvipanda))
|
||||||
|
- Reword documentation sentence [#970](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/970) ([@davidalber](https://github.com/davidalber), [@consideRatio](https://github.com/consideRatio))
|
||||||
|
- Fix typo and replace word [#969](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/969) ([@davidalber](https://github.com/davidalber), [@consideRatio](https://github.com/consideRatio))
|
||||||
|
- Fix URL syntax in nativeauth.md [#949](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/949) ([@pdebuyl](https://github.com/pdebuyl), [@minrk](https://github.com/minrk))
|
||||||
|
- adapt install documentation for new /lab default interface [#935](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/935) ([@schwebke](https://github.com/schwebke), [@minrk](https://github.com/minrk))
|
||||||
|
|
||||||
|
#### Continuous integration improvements
|
||||||
|
|
||||||
|
- ci: cache pip only when no container is used [#997](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/997) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- ci: run unit tests in ubuntu-24.04 github actions environment as well [#993](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/993) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- ci: add tests for debian 12 and ubuntu 24.04 [#967](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/967) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- build(deps): bump actions/cache from 3 to 4 [#961](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/961) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- build(deps): bump codecov/codecov-action from 3 to 4 [#960](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/960) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- build(deps): bump actions/setup-python from 4 to 5 [#958](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/958) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- build(deps): bump actions/checkout from 3 to 4 [#943](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/943) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
|
||||||
|
#### Contributors to this release
|
||||||
|
|
||||||
|
The following people contributed discussions, new ideas, code and documentation contributions, and review.
|
||||||
|
See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports).
|
||||||
|
|
||||||
|
([GitHub contributors page for this release](https://github.com/jupyterhub/the-littlest-jupyterhub/graphs/contributors?from=2023-08-11&to=2024-09-30&type=c))
|
||||||
|
|
||||||
|
@consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AconsideRatio+updated%3A2023-08-11..2024-09-30&type=Issues)) | @davidalber ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Adavidalber+updated%3A2023-08-11..2024-09-30&type=Issues)) | @josedaudi ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajosedaudi+updated%3A2023-08-11..2024-09-30&type=Issues)) | @jrdnbradford ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajrdnbradford+updated%3A2023-08-11..2024-09-30&type=Issues)) | @kiliansinger ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Akiliansinger+updated%3A2023-08-11..2024-09-30&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Amanics+updated%3A2023-08-11..2024-09-30&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aminrk+updated%3A2023-08-11..2024-09-30&type=Issues)) | @MridulS ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AMridulS+updated%3A2023-08-11..2024-09-30&type=Issues)) | @pdebuyl ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Apdebuyl+updated%3A2023-08-11..2024-09-30&type=Issues)) | @schwebke ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aschwebke+updated%3A2023-08-11..2024-09-30&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ayuvipanda+updated%3A2023-08-11..2024-09-30&type=Issues))
|
||||||
|
|
||||||
## 1.0
|
## 1.0
|
||||||
|
|
||||||
### 1.0.0b1 - 2023-07-07
|
### 1.0.0 - 2023-08-11
|
||||||
|
|
||||||
````{warning}
|
|
||||||
This is a beta release.
|
|
||||||
|
|
||||||
To upgrade to it, after having read the breaking changes below, you can do the
|
|
||||||
following from a terminal on a machine TLJH is installed.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# This should NOT be run from a JupyterHub started user server, but should
|
|
||||||
# run from a standalone terminal session in the machine where TLJH has been
|
|
||||||
# installed.
|
|
||||||
curl -L https://tljh.jupyter.org/bootstrap.py \
|
|
||||||
| sudo python3 - \
|
|
||||||
--version=1.0.0b1
|
|
||||||
```
|
|
||||||
````
|
|
||||||
|
|
||||||
This release bundles with the latest available software from the JupyterHub
|
This release bundles with the latest available software from the JupyterHub
|
||||||
ecosystem.
|
ecosystem.
|
||||||
@@ -27,9 +136,11 @@ The TLJH project now has tests to verify upgrades of installations between
|
|||||||
releases and procedures with automation to make releases. Going onwards, TLJH
|
releases and procedures with automation to make releases. Going onwards, TLJH
|
||||||
installations of version 0.2.0 and later are meant to be easy to upgrade.
|
installations of version 0.2.0 and later are meant to be easy to upgrade.
|
||||||
|
|
||||||
|
For instructions on how to make an upgrade, see [](howto-admin-upgrade-tljh).
|
||||||
|
|
||||||
#### Breaking changes
|
#### Breaking changes
|
||||||
|
|
||||||
- JupyterHub 1.\* has been upgraded to >=4.0.1,<5
|
- JupyterHub 1.\* has been upgraded to >=4.0.2,<5
|
||||||
- This upgrade requires user servers to be restarted if they were running
|
- This upgrade requires user servers to be restarted if they were running
|
||||||
during the upgrade.
|
during the upgrade.
|
||||||
- Refer to the [JupyterHub changelog] for details where you pay attention to
|
- Refer to the [JupyterHub changelog] for details where you pay attention to
|
||||||
@@ -44,6 +155,9 @@ installations of version 0.2.0 and later are meant to be easy to upgrade.
|
|||||||
- The configured JupyterHub Spawner class `jupyterhub-systemdspawner` has been
|
- The configured JupyterHub Spawner class `jupyterhub-systemdspawner` has been
|
||||||
upgraded to a new major version, but no breaking change are expected to be
|
upgraded to a new major version, but no breaking change are expected to be
|
||||||
noticed for users of this distribution.
|
noticed for users of this distribution.
|
||||||
|
- User servers now launch into `/lab` by default, to revert this a JupyterHub
|
||||||
|
admin user can do `sudo tljh-config set user_environment.default_app classic`
|
||||||
|
or set the JupyterHub config `c.Spawner.default_url` directly.
|
||||||
|
|
||||||
[jupyterhub changelog]: https://jupyterhub.readthedocs.io/en/stable/changelog.html
|
[jupyterhub changelog]: https://jupyterhub.readthedocs.io/en/stable/changelog.html
|
||||||
|
|
||||||
@@ -62,29 +176,29 @@ user environment. The hub environment's dependencies are on the other hand
|
|||||||
always upgraded to the latest version within the specified version range defined
|
always upgraded to the latest version within the specified version range defined
|
||||||
in [tljh/requirements-hub-env.txt] and seen below.
|
in [tljh/requirements-hub-env.txt] and seen below.
|
||||||
|
|
||||||
[tljh/requirements-user-env-extras.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/1.0.0b1/tljh/requirements-user-env-extras.txt
|
[tljh/requirements-user-env-extras.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/1.0.0/tljh/requirements-user-env-extras.txt
|
||||||
[tljh/requirements-hub-env.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/1.0.0b1/tljh/requirements-hub-env.txt
|
[tljh/requirements-hub-env.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/1.0.0/tljh/requirements-hub-env.txt
|
||||||
|
|
||||||
The changes in the respective environments between TLJH version 0.2.0 and
|
The changes in the respective environments between TLJH version 0.2.0 and 1.0.0
|
||||||
1.0.0b1 are summarized below.
|
are summarized below.
|
||||||
|
|
||||||
| Dependency changes in the _hub environment_ | Version in 0.2.0 | Version in 1.0.0b1 | Changelog link | Note |
|
| Dependency changes in the _hub environment_ | Version in 0.2.0 | Version in 1.0.0 | Changelog link | Note |
|
||||||
| ------------------------------------------------------------------------------ | ---------------- | ------------------ | ---------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
| ------------------------------------------------------------------------------ | ---------------- | ---------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||||
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | 1.\* | >=4.0.1,<5 | [Changelog](https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html) | Running in the `jupyterhub` systemd service |
|
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | 1.\* | >=4.0.2,<5 | [Changelog](https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html) | Running in the `jupyterhub` systemd service |
|
||||||
| [traefik](https://github.com/traefik/traefik) | 1.7.33 | 2.10.1 | [Changelog](https://github.com/traefik/traefik/blob/master/CHANGELOG.md) | Running in the `traefik` systemd service |
|
| [traefik](https://github.com/traefik/traefik) | 1.7.33 | 2.10.1 | [Changelog](https://github.com/traefik/traefik/blob/master/CHANGELOG.md) | Running in the `traefik` systemd service |
|
||||||
| [traefik-proxy](https://github.com/jupyterhub/traefik-proxy) | 0.3.\* | >=1.1.0,<2 | [Changelog](https://jupyterhub-traefik-proxy.readthedocs.io/en/latest/changelog.html) | Run by jupyterhub, controls `traefik` |
|
| [traefik-proxy](https://github.com/jupyterhub/traefik-proxy) | 0.3.\* | >=1.1.0,<2 | [Changelog](https://jupyterhub-traefik-proxy.readthedocs.io/en/latest/changelog.html) | Run by jupyterhub, controls `traefik` |
|
||||||
| [systemdspawner](https://github.com/jupyterhub/systemdspawner) | 0.16.\* | >=1.0.1,<2 | [Changelog](https://github.com/jupyterhub/systemdspawner/blob/master/CHANGELOG.md) | Run by jupyterhub, controls user servers via systemd |
|
| [systemdspawner](https://github.com/jupyterhub/systemdspawner) | 0.16.\* | >=1.0.1,<2 | [Changelog](https://github.com/jupyterhub/systemdspawner/blob/master/CHANGELOG.md) | Run by jupyterhub, controls user servers via systemd |
|
||||||
| [jupyterhub-idle-culler](https://github.com/jupyterhub/jupyterhub-idle-culler) | 1.\* | >=1.2.1,<2 | [Changelog](https://github.com/jupyterhub/jupyterhub-idle-culler/blob/main/CHANGELOG.md) | Run by jupyterhub, stops inactivate servers etc. |
|
| [jupyterhub-idle-culler](https://github.com/jupyterhub/jupyterhub-idle-culler) | 1.\* | >=1.2.1,<2 | [Changelog](https://github.com/jupyterhub/jupyterhub-idle-culler/blob/main/CHANGELOG.md) | Run by jupyterhub, stops inactivate servers etc. |
|
||||||
| [firstuseauthenticator](https://github.com/jupyterhub/firstuseauthenticator) | 1.\* | >=1.0.0,<2 | [Changelog](https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html) | An optional way to authenticate users |
|
| [firstuseauthenticator](https://github.com/jupyterhub/firstuseauthenticator) | 1.\* | >=1.0.0,<2 | [Changelog](https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html) | An optional way to authenticate users |
|
||||||
| [tmpauthenticator](https://github.com/jupyterhub/tmpauthenticator) | 0.6.\* | >=1.0.0,<2 | [Changelog](https://github.com/jupyterhub/tmpauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
| [tmpauthenticator](https://github.com/jupyterhub/tmpauthenticator) | 0.6.\* | >=1.0.0,<2 | [Changelog](https://github.com/jupyterhub/tmpauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||||
| [nativeauthenticator](https://github.com/jupyterhub/nativeauthenticator) | 1.\* | >=1.2.0,<2 | [Changelog](https://github.com/jupyterhub/nativeauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
| [nativeauthenticator](https://github.com/jupyterhub/nativeauthenticator) | 1.\* | >=1.2.0,<2 | [Changelog](https://github.com/jupyterhub/nativeauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||||
| [oauthenticator](https://github.com/jupyterhub/oauthenticator) | 14.\* | >=16.0.2,<17 | [Changelog](https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html) | An optional way to authenticate users |
|
| [oauthenticator](https://github.com/jupyterhub/oauthenticator) | 14.\* | >=16.0.4,<17 | [Changelog](https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html) | An optional way to authenticate users |
|
||||||
| [ldapauthenticator](https://github.com/jupyterhub/ldapauthenticator) | 1.\* | >=1.3.2,<2 | [Changelog](https://github.com/jupyterhub/ldapauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
| [ldapauthenticator](https://github.com/jupyterhub/ldapauthenticator) | 1.\* | >=1.3.2,<2 | [Changelog](https://github.com/jupyterhub/ldapauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||||
| [pip](https://github.com/pypa/pip) | 21.3.\* | >=23.1.2 | [Changelog](https://pip.pypa.io/en/stable/news/) | - |
|
| [pip](https://github.com/pypa/pip) | 21.3.\* | >=23.1.2 | [Changelog](https://pip.pypa.io/en/stable/news/) | - |
|
||||||
|
|
||||||
| Dependency changes in the _user environment_ | Version in 0.2.0 | Version in 1.0.0 | Changelog link | Note |
|
| Dependency changes in the _user environment_ | Version in 0.2.0 | Version in 1.0.0 | Changelog link | Note |
|
||||||
| -------------------------------------------------------- | ---------------- | ---------------- | --------------------------------------------------------------------------------- | ------------------------ |
|
| -------------------------------------------------------- | ---------------- | ---------------- | --------------------------------------------------------------------------------- | ------------------------ |
|
||||||
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | 1.\* | >=4.0.1,<5 | [Changelog](https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html) | Always upgraded. |
|
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | 1.\* | >=4.0.2,<5 | [Changelog](https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html) | Always upgraded. |
|
||||||
| [pip](https://github.com/pypa/pip) | \* | >=23.1.2 | [Changelog](https://pip.pypa.io/en/stable/news/) | Only upgraded if needed. |
|
| [pip](https://github.com/pypa/pip) | \* | >=23.1.2 | [Changelog](https://pip.pypa.io/en/stable/news/) | Only upgraded if needed. |
|
||||||
| [conda](https://docs.conda.io/projects/conda/en/stable/) | 0.16.0 | >=0.16.0 | [Changelog](https://docs.conda.io/projects/conda/en/stable/release-notes.html) | Only upgraded if needed. |
|
| [conda](https://docs.conda.io/projects/conda/en/stable/) | 0.16.0 | >=0.16.0 | [Changelog](https://docs.conda.io/projects/conda/en/stable/release-notes.html) | Only upgraded if needed. |
|
||||||
| [mamba](https://mamba.readthedocs.io/en/latest/) | 4.10.3 | >=4.10.0 | [Changelog](https://github.com/mamba-org/mamba/blob/main/CHANGELOG.md) | Only upgraded if needed. |
|
| [mamba](https://mamba.readthedocs.io/en/latest/) | 4.10.3 | >=4.10.0 | [Changelog](https://github.com/mamba-org/mamba/blob/main/CHANGELOG.md) | Only upgraded if needed. |
|
||||||
@@ -101,6 +215,8 @@ The changes in the respective environments between TLJH version 0.2.0 and
|
|||||||
|
|
||||||
#### Maintenance and upkeep improvements
|
#### Maintenance and upkeep improvements
|
||||||
|
|
||||||
|
- Update Notebook, JupyterLab, Jupyter Resource Usage [#928](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/928) ([@jtpio](https://github.com/jtpio), [@consideRatio](https://github.com/consideRatio))
|
||||||
|
- Launch into `/lab` by default by changing TLJH config's default value [#775](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/775) ([@raybellwaves](https://github.com/raybellwaves), [@consideRatio](https://github.com/consideRatio), [@GeorgianaElena](https://github.com/GeorgianaElena), [@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||||
- breaking: update oauthenticator from 15.1.0 to >=16.0.2,<17, make tljh auth docs link out [#924](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/924) ([@consideRatio](https://github.com/consideRatio), [@manics](https://github.com/manics), [@minrk](https://github.com/minrk))
|
- breaking: update oauthenticator from 15.1.0 to >=16.0.2,<17, make tljh auth docs link out [#924](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/924) ([@consideRatio](https://github.com/consideRatio), [@manics](https://github.com/manics), [@minrk](https://github.com/minrk))
|
||||||
- test refactor: add comment about python/conda/mamba [#921](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/921) ([@consideRatio](https://github.com/consideRatio))
|
- test refactor: add comment about python/conda/mamba [#921](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/921) ([@consideRatio](https://github.com/consideRatio))
|
||||||
- --force-reinstall old conda to ensure it's working before we try to install conda packages [#920](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/920) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
- --force-reinstall old conda to ensure it's working before we try to install conda packages [#920](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/920) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||||
@@ -129,6 +245,8 @@ The changes in the respective environments between TLJH version 0.2.0 and
|
|||||||
|
|
||||||
#### Documentation improvements
|
#### Documentation improvements
|
||||||
|
|
||||||
|
- docs: add docs about environments and upgrades [#932](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/932) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||||
|
- Add `JupyterLab` setting overrides docs [#922](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/922) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio))
|
||||||
- Quote `pwd` to prevent error if dir has spaces [#917](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/917) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio))
|
- Quote `pwd` to prevent error if dir has spaces [#917](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/917) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio))
|
||||||
- Google Cloud troubleshooting and configuration updates [#906](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/906) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio))
|
- Google Cloud troubleshooting and configuration updates [#906](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/906) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio))
|
||||||
- Add user env doc files [#902](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/902) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio))
|
- Add user env doc files [#902](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/902) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio))
|
||||||
@@ -148,9 +266,9 @@ The changes in the respective environments between TLJH version 0.2.0 and
|
|||||||
The following people contributed discussions, new ideas, code and documentation contributions, and review.
|
The following people contributed discussions, new ideas, code and documentation contributions, and review.
|
||||||
See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports).
|
See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports).
|
||||||
|
|
||||||
([GitHub contributors page for this release](https://github.com/jupyterhub/the-littlest-jupyterhub/graphs/contributors?from=2023-02-27&to=2023-06-09&type=c))
|
([GitHub contributors page for this release](https://github.com/jupyterhub/the-littlest-jupyterhub/graphs/contributors?from=2023-02-27&to=2023-08-11&type=c))
|
||||||
|
|
||||||
@adonm ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aadonm+updated%3A2023-02-27..2023-06-09&type=Issues)) | @audiodude ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aaudiodude+updated%3A2023-02-27..2023-06-09&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AconsideRatio+updated%3A2023-02-27..2023-06-09&type=Issues)) | @eingemaischt ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aeingemaischt+updated%3A2023-02-27..2023-06-09&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AGeorgianaElena+updated%3A2023-02-27..2023-06-09&type=Issues)) | @Hannnsen ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AHannnsen+updated%3A2023-02-27..2023-06-09&type=Issues)) | @jawiv ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajawiv+updated%3A2023-02-27..2023-06-09&type=Issues)) | @jochym ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajochym+updated%3A2023-02-27..2023-06-09&type=Issues)) | @jrdnbradford ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajrdnbradford+updated%3A2023-02-27..2023-06-09&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Amanics+updated%3A2023-02-27..2023-06-09&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aminrk+updated%3A2023-02-27..2023-06-09&type=Issues)) | @MridulS ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AMridulS+updated%3A2023-02-27..2023-06-09&type=Issues)) | @nsurleraux-railnova ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ansurleraux-railnova+updated%3A2023-02-27..2023-06-09&type=Issues)) | @Rom1deTroyes ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3ARom1deTroyes+updated%3A2023-02-27..2023-06-09&type=Issues)) | @wjcapehart ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Awjcapehart+updated%3A2023-02-27..2023-06-09&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ayuvipanda+updated%3A2023-02-27..2023-06-09&type=Issues))
|
@adonm ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aadonm+updated%3A2023-02-27..2023-08-11&type=Issues)) | @audiodude ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aaudiodude+updated%3A2023-02-27..2023-08-11&type=Issues)) | @choldgraf ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Acholdgraf+updated%3A2023-02-27..2023-08-11&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AconsideRatio+updated%3A2023-02-27..2023-08-11&type=Issues)) | @eingemaischt ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aeingemaischt+updated%3A2023-02-27..2023-08-11&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AGeorgianaElena+updated%3A2023-02-27..2023-08-11&type=Issues)) | @Hannnsen ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AHannnsen+updated%3A2023-02-27..2023-08-11&type=Issues)) | @jawiv ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajawiv+updated%3A2023-02-27..2023-08-11&type=Issues)) | @jochym ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajochym+updated%3A2023-02-27..2023-08-11&type=Issues)) | @jrdnbradford ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajrdnbradford+updated%3A2023-02-27..2023-08-11&type=Issues)) | @jtpio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajtpio+updated%3A2023-02-27..2023-08-11&type=Issues)) | @kevmk04 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Akevmk04+updated%3A2023-02-27..2023-08-11&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Amanics+updated%3A2023-02-27..2023-08-11&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aminrk+updated%3A2023-02-27..2023-08-11&type=Issues)) | @MridulS ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AMridulS+updated%3A2023-02-27..2023-08-11&type=Issues)) | @nsurleraux-railnova ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ansurleraux-railnova+updated%3A2023-02-27..2023-08-11&type=Issues)) | @raybellwaves ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Araybellwaves+updated%3A2023-02-27..2023-08-11&type=Issues)) | @Rom1deTroyes ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3ARom1deTroyes+updated%3A2023-02-27..2023-08-11&type=Issues)) | @wjcapehart ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Awjcapehart+updated%3A2023-02-27..2023-08-11&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ayuvipanda+updated%3A2023-02-27..2023-08-11&type=Issues))
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ Topic guides provide in-depth explanations of specific topics.
|
|||||||
|
|
||||||
whentouse
|
whentouse
|
||||||
requirements
|
requirements
|
||||||
|
three-environments
|
||||||
security
|
security
|
||||||
customizing-installer
|
customizing-installer
|
||||||
installer-actions
|
installer-actions
|
||||||
|
installer-upgrade-actions
|
||||||
tljh-config
|
tljh-config
|
||||||
authenticator-configuration
|
authenticator-configuration
|
||||||
escape-hatch
|
escape-hatch
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ sudo rm -rf /opt/tljh/hub
|
|||||||
|
|
||||||
## User environment
|
## User environment
|
||||||
|
|
||||||
By default, a `mambaforge` conda environment is installed in `/opt/tljh/user`. This contains
|
By default, a `miniforge` conda environment is installed in `/opt/tljh/user`. This contains
|
||||||
the notebook interface used to launch all users, and the various packages available to all
|
the notebook interface used to launch all users, and the various packages available to all
|
||||||
users. The environment is owned by the `root` user. JupyterHub admins may use
|
users. The environment is owned by the `root` user. JupyterHub admins may use
|
||||||
to `sudo -E conda install` or `sudo -E pip install` packages into this environment.
|
to `sudo -E conda install` or `sudo -E pip install` packages into this environment.
|
||||||
|
|||||||
30
docs/topic/installer-upgrade-actions.md
Normal file
30
docs/topic/installer-upgrade-actions.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
(topic-installer-upgrade-actions)=
|
||||||
|
|
||||||
|
# What is done during an upgrade of TLJH?
|
||||||
|
|
||||||
|
Once TLJH has been installed, it should be possible to upgrade the installation.
|
||||||
|
This documentation is meant to capture the changes made during an upgrade.
|
||||||
|
|
||||||
|
```{versionchanged} 1.0.0
|
||||||
|
Ensuring upgrades work has only been done since 1.0.0 upgrading from version
|
||||||
|
0.2.0.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changes to the system environment
|
||||||
|
|
||||||
|
The [system environment](system-environment) is not meant to be influenced
|
||||||
|
unless explicitly mentioned in the changelog, typically only during major
|
||||||
|
version upgrades.
|
||||||
|
|
||||||
|
## Changes to the hub environment
|
||||||
|
|
||||||
|
The [hub environment](hub-environment) gets several packages upgraded based on
|
||||||
|
version ranges specified in [tljh/requirements-hub-env.txt].
|
||||||
|
|
||||||
|
## Changes to the user environment
|
||||||
|
|
||||||
|
The [user environment](user-environment) gets is `jupyterhub` package upgraded,
|
||||||
|
but no other packages gets upgraded unless explicitly mentioned in the
|
||||||
|
changelog, typically only during major version upgrades.
|
||||||
|
|
||||||
|
[tljh/requirements-hub-env.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/HEAD/tljh/requirements-hub-env.txt
|
||||||
76
docs/topic/three-environments.md
Normal file
76
docs/topic/three-environments.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
(topic-three-environments)=
|
||||||
|
|
||||||
|
# The system, hub, and user environments
|
||||||
|
|
||||||
|
TLJH's documentation mentions the _system environment_, the _hub environment_,
|
||||||
|
and the _user environment_. This section will introduce what is meant with that
|
||||||
|
and clarify the distinctions between the environments.
|
||||||
|
|
||||||
|
(system-environment)=
|
||||||
|
|
||||||
|
## The system environment
|
||||||
|
|
||||||
|
When this documentation mentions the _system environment_, it refers to the
|
||||||
|
Linux environment with its installed `apt` packages, users in `/etc/passwd`,
|
||||||
|
etc.
|
||||||
|
|
||||||
|
A part of the system environment is a Python environment setup via the `apt`
|
||||||
|
package `python` installed by default in Linux distributions supported by TLJH.
|
||||||
|
To be specific, we can refer to this as the _system's Python environment_.
|
||||||
|
|
||||||
|
If you would do `sudo python3 -m pip install <something>` you would end up
|
||||||
|
installing something in the system's Python environment, and that would not be
|
||||||
|
available in the hub environment or the user environment.
|
||||||
|
|
||||||
|
The system's Python environment is only used by TLJH to run the `bootstrap.py`
|
||||||
|
script downloaded as part of installing or upgrading TLJH. This script is also
|
||||||
|
responsible for setting up the hub environment.
|
||||||
|
|
||||||
|
(hub-environment)=
|
||||||
|
|
||||||
|
## The hub environment
|
||||||
|
|
||||||
|
The _hub environment_ is a [virtual Python environment] setup in `/opt/tljh/hub`
|
||||||
|
by the `bootstrap.py` script using the system's Python environment during TLJH
|
||||||
|
installation.
|
||||||
|
|
||||||
|
The hub environment has Python packages installed in it related to running
|
||||||
|
JupyterHub itself such as an JupyterHub authenticator package, but it doesn't
|
||||||
|
include packages to start user servers like JupyterLab.
|
||||||
|
|
||||||
|
When TLJH is installed/upgraded, the packages listed in
|
||||||
|
[tljh/requirements-hub-env.txt] are installed/upgraded in this environment.
|
||||||
|
|
||||||
|
If you would do `sudo /opt/tljh/hub/bin/python3 -m pip install <something>` you
|
||||||
|
would end up installing something in the hub environment, and that would not be
|
||||||
|
available in the system's Python environment or the user environment.
|
||||||
|
|
||||||
|
[virtual Python environment]: https://docs.python.org/3/library/venv.html
|
||||||
|
|
||||||
|
[tljh/requirements-hub-env.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/HEAD/tljh/requirements-hub-env.txt
|
||||||
|
|
||||||
|
(user-environment)=
|
||||||
|
|
||||||
|
## The user environment
|
||||||
|
|
||||||
|
The _user environment_ is a Python environment setup in `/opt/tljh/user` by the
|
||||||
|
TLJH installer during TLJH installation. The user environment is not a virtual
|
||||||
|
environment because an entirely separate installation of Python has been made
|
||||||
|
for it.
|
||||||
|
|
||||||
|
The user environment has packages installed in it related to running individual
|
||||||
|
jupyter servers, such as `jupyterlab`.
|
||||||
|
|
||||||
|
When TLJH is _installed_, the packages listed in
|
||||||
|
[tljh/requirements-user-env.txt] are installed in this environment. When TLJH is
|
||||||
|
_upgraded_ though, as little as possible is done to this environment. Typically
|
||||||
|
only `jupyterhub` is upgraded to match the version in the hub environment. If
|
||||||
|
upgrading to a new major version of TLJH, then something small may be done
|
||||||
|
besides this, and then it should be described the changelog.
|
||||||
|
|
||||||
|
If you would do `sudo /opt/tljh/user/bin/python3 -m pip install <something>`, or
|
||||||
|
from a user server's terminal do `sudo -E pip install <something>` you would end
|
||||||
|
up installing something in the user environment, and that would not be available
|
||||||
|
in the system's Python environment or the hub environment.
|
||||||
|
|
||||||
|
[tljh/requirements-user-env-extras.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/HEAD/tljh/requirements-user-env-extras.txt
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
(troubleshooting)=
|
||||||
|
|
||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
|
|
||||||
In time, all systems have issues that need to be debugged. Troubleshooting
|
In time, all systems have issues that need to be debugged. Troubleshooting
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Systemd inside a Docker container, for CI only
|
# Systemd inside a Docker container, for CI only
|
||||||
ARG BASE_IMAGE=ubuntu:20.04
|
ARG BASE_IMAGE=ubuntu:22.04
|
||||||
FROM $BASE_IMAGE
|
FROM $BASE_IMAGE
|
||||||
|
|
||||||
# DEBIAN_FRONTEND is set to avoid being asked for input and hang during build:
|
# DEBIAN_FRONTEND is set to avoid being asked for input and hang during build:
|
||||||
@@ -29,8 +29,8 @@ RUN systemctl set-default multi-user.target
|
|||||||
STOPSIGNAL SIGRTMIN+3
|
STOPSIGNAL SIGRTMIN+3
|
||||||
|
|
||||||
# Uncomment these lines for a development install
|
# Uncomment these lines for a development install
|
||||||
#ENV TLJH_BOOTSTRAP_DEV=yes
|
# ENV TLJH_BOOTSTRAP_DEV=yes
|
||||||
#ENV TLJH_BOOTSTRAP_PIP_SPEC=/srv/src
|
# ENV TLJH_BOOTSTRAP_PIP_SPEC=/srv/src
|
||||||
#ENV PATH=/opt/tljh/hub/bin:${PATH}
|
# ENV PATH=/opt/tljh/hub/bin:${PATH}
|
||||||
|
|
||||||
CMD ["/bin/bash", "-c", "exec /lib/systemd/systemd --log-target=journal 3>&1"]
|
CMD ["/bin/bash", "-c", "exec /lib/systemd/systemd --log-target=journal 3>&1"]
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
"""
|
"""
|
||||||
Simplest plugin that exercises all the hooks defined in tljh/hooks.py.
|
Simplest plugin that exercises all the hooks defined in tljh/hooks.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from tljh.hooks import hookimpl
|
from tljh.hooks import hookimpl
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def tljh_extra_user_conda_packages():
|
def tljh_extra_user_conda_packages():
|
||||||
return ["tqdm"]
|
# tqdm installs from the conda-forge channel (https://conda-forge.org/packages/)
|
||||||
|
# csvtk installs from the bioconda channel (https://bioconda.github.io/conda-package_index.html)
|
||||||
|
return ["tqdm", "csvtk"]
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def tljh_extra_user_conda_channels():
|
||||||
|
return ["conda-forge", "bioconda"]
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def tljh_extra_user_pip_packages():
|
def tljh_extra_user_pip_packages():
|
||||||
return ["django"]
|
return ["simplejson"]
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ FIXME: The last test stands out and could be part of the other tests, and the
|
|||||||
first two could be more like unit tests. Ideally, this file is
|
first two could be more like unit tests. Ideally, this file is
|
||||||
significantly reduced.
|
significantly reduced.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -84,9 +85,9 @@ def test_ubuntu_too_old():
|
|||||||
"""
|
"""
|
||||||
Error with a useful message when running in older Ubuntu
|
Error with a useful message when running in older Ubuntu
|
||||||
"""
|
"""
|
||||||
output = _run_bootstrap_in_container("ubuntu:18.04", False)
|
output = _run_bootstrap_in_container("ubuntu:20.04", False)
|
||||||
_stop_container()
|
_stop_container()
|
||||||
assert output.stdout == "The Littlest JupyterHub requires Ubuntu 20.04 or higher\n"
|
assert output.stdout == "The Littlest JupyterHub requires Ubuntu 22.04 or higher\n"
|
||||||
assert output.returncode == 1
|
assert output.returncode == 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
@@ -7,13 +8,13 @@ def test_serverextensions():
|
|||||||
"""
|
"""
|
||||||
# jupyter-serverextension writes to stdout and stderr weirdly
|
# jupyter-serverextension writes to stdout and stderr weirdly
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
["/opt/tljh/user/bin/jupyter-serverextension", "list", "--sys-prefix"],
|
["/opt/tljh/user/bin/jupyter-server", "extension", "list", "--sys-prefix"],
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
"jupyterlab 3.",
|
"jupyterlab",
|
||||||
"nbgitpuller 1.",
|
"nbgitpuller",
|
||||||
"jupyter_resource_usage",
|
"jupyter_resource_usage",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -21,27 +22,25 @@ def test_serverextensions():
|
|||||||
assert e in proc.stderr.decode()
|
assert e in proc.stderr.decode()
|
||||||
|
|
||||||
|
|
||||||
def test_nbextensions():
|
def test_labextensions():
|
||||||
"""
|
"""
|
||||||
Validate nbextensions we want are installed & enabled
|
Validate JupyterLab extensions we want are installed & enabled
|
||||||
"""
|
"""
|
||||||
# jupyter-nbextension writes to stdout and stderr weirdly
|
# jupyter-labextension writes to stdout and stderr weirdly
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
["/opt/tljh/user/bin/jupyter-nbextension", "list", "--sys-prefix"],
|
["/opt/tljh/user/bin/jupyter-labextension", "list"],
|
||||||
stderr=subprocess.PIPE,
|
capture_output=True,
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
"jupyter_resource_usage/main",
|
"@jupyter-server/resource-usage",
|
||||||
# This is what ipywidgets nbextension is called
|
# This is what ipywidgets lab extension is called
|
||||||
"jupyter-js-widgets/extension",
|
"@jupyter-widgets/jupyterlab-manager",
|
||||||
]
|
]
|
||||||
|
|
||||||
for e in extensions:
|
for e in extensions:
|
||||||
assert f"{e} \x1b[32m enabled \x1b[0m" in proc.stdout.decode()
|
# jupyter labextension lists outputs to stderr
|
||||||
|
out = proc.stderr.decode()
|
||||||
# Ensure we have 'OK' messages in our stdout, to make sure everything is importable
|
enabled_ok_pattern = re.compile(rf"{e}.*enabled.*OK")
|
||||||
assert proc.stderr.decode() == " - Validating: \x1b[32mOK\x1b[0m\n" * len(
|
matches = enabled_ok_pattern.search(out)
|
||||||
extensions
|
assert matches is not None
|
||||||
)
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def test_hub_version():
|
|||||||
r = requests.get(HUB_URL + "/hub/api")
|
r = requests.get(HUB_URL + "/hub/api")
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
info = r.json()
|
info = r.json()
|
||||||
assert V("4") <= V(info["version"]) <= V("5")
|
assert V("5.1") <= V(info["version"]) <= V("6")
|
||||||
|
|
||||||
|
|
||||||
async def test_user_code_execute():
|
async def test_user_code_execute():
|
||||||
@@ -59,9 +59,9 @@ async def test_user_code_execute():
|
|||||||
|
|
||||||
async with User(username, HUB_URL, partial(login_dummy, password="")) as u:
|
async with User(username, HUB_URL, partial(login_dummy, password="")) as u:
|
||||||
assert await u.login()
|
assert await u.login()
|
||||||
await u.ensure_server_simulate(timeout=60, spawn_refresh_time=5)
|
assert await u.ensure_server_simulate(timeout=60, spawn_refresh_time=5)
|
||||||
await u.start_kernel()
|
assert await u.start_kernel()
|
||||||
await u.assert_code_output("5 * 4", "20", 5, 5)
|
assert await u.assert_code_output("5 * 4", "20", 5, 5)
|
||||||
|
|
||||||
|
|
||||||
async def test_user_server_started_with_custom_base_url():
|
async def test_user_server_started_with_custom_base_url():
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""tests for the proxy"""
|
"""tests for the proxy"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import ssl
|
import ssl
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Test the plugin in integration-tests/plugins/simplest that makes use of all tljh
|
Test the plugin in integration-tests/plugins/simplest that makes use of all tljh
|
||||||
recognized plugin hooks that are defined in tljh/hooks.py.
|
recognized plugin hooks that are defined in tljh/hooks.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
@@ -19,13 +20,24 @@ def test_tljh_extra_user_conda_packages():
|
|||||||
|
|
||||||
|
|
||||||
def test_tljh_extra_user_pip_packages():
|
def test_tljh_extra_user_pip_packages():
|
||||||
subprocess.check_call([f"{USER_ENV_PREFIX}/bin/python3", "-c", "import django"])
|
subprocess.check_call([f"{USER_ENV_PREFIX}/bin/python3", "-c", "import simplejson"])
|
||||||
|
|
||||||
|
|
||||||
def test_tljh_extra_hub_pip_packages():
|
def test_tljh_extra_hub_pip_packages():
|
||||||
subprocess.check_call([f"{HUB_ENV_PREFIX}/bin/python3", "-c", "import there"])
|
subprocess.check_call([f"{HUB_ENV_PREFIX}/bin/python3", "-c", "import there"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_conda_packages():
|
||||||
|
"""
|
||||||
|
Test extra user conda packages are installed from multiple channels.
|
||||||
|
|
||||||
|
- tqdm installs from the conda-forge channel (https://conda-forge.org/packages/)
|
||||||
|
- csvtk installs from the bioconda channel (https://bioconda.github.io/conda-package_index.html)
|
||||||
|
"""
|
||||||
|
subprocess.check_call([f"{USER_ENV_PREFIX}/bin/python3", "-c", "import tqdm"])
|
||||||
|
subprocess.check_call([f"{USER_ENV_PREFIX}/bin/csvtk", "cat", "--help"])
|
||||||
|
|
||||||
|
|
||||||
def test_tljh_extra_apt_packages():
|
def test_tljh_extra_apt_packages():
|
||||||
assert os.path.exists("/usr/games/sl")
|
assert os.path.exists("/usr/games/sl")
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,10 @@ profile = "black"
|
|||||||
# target-version should be all supported versions, see
|
# target-version should be all supported versions, see
|
||||||
# https://github.com/psf/black/issues/751#issuecomment-473066811
|
# https://github.com/psf/black/issues/751#issuecomment-473066811
|
||||||
target_version = [
|
target_version = [
|
||||||
"py36",
|
|
||||||
"py37",
|
|
||||||
"py38",
|
|
||||||
"py39",
|
"py39",
|
||||||
"py310",
|
"py310",
|
||||||
"py311",
|
"py311",
|
||||||
|
"py312",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +65,7 @@ omit = [
|
|||||||
github_url = "https://github.com/jupyterhub/the-littlest-jupyterhub"
|
github_url = "https://github.com/jupyterhub/the-littlest-jupyterhub"
|
||||||
|
|
||||||
[tool.tbump.version]
|
[tool.tbump.version]
|
||||||
current = "1.0.0b1"
|
current = "2.0.0b1"
|
||||||
regex = '''
|
regex = '''
|
||||||
(?P<major>\d+)
|
(?P<major>\d+)
|
||||||
\.
|
\.
|
||||||
|
|||||||
8
setup.py
8
setup.py
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="the-littlest-jupyterhub",
|
name="the-littlest-jupyterhub",
|
||||||
version="1.0.0b1",
|
version="2.0.0b1",
|
||||||
description="A small JupyterHub distribution",
|
description="A small JupyterHub distribution",
|
||||||
url="https://github.com/jupyterhub/the-littlest-jupyterhub",
|
url="https://github.com/jupyterhub/the-littlest-jupyterhub",
|
||||||
author="Jupyter Development Team",
|
author="Jupyter Development Team",
|
||||||
@@ -10,14 +10,16 @@ setup(
|
|||||||
license="3 Clause BSD",
|
license="3 Clause BSD",
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
|
python_requires=">=3.9",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"ruamel.yaml==0.17.*",
|
"ruamel.yaml==0.18.*",
|
||||||
"jinja2",
|
"jinja2",
|
||||||
"pluggy==1.*",
|
"pluggy==1.*",
|
||||||
"backoff",
|
"backoff",
|
||||||
|
"filelock",
|
||||||
"requests",
|
"requests",
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"jupyterhub-traefik-proxy==1.*",
|
"jupyterhub-traefik-proxy==2.*",
|
||||||
],
|
],
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""pytest fixtures"""
|
"""pytest fixtures"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import types
|
import types
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Test conda commandline wrappers
|
Test conda commandline wrappers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -13,9 +14,9 @@ from tljh import conda, installer
|
|||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def prefix():
|
def prefix():
|
||||||
"""
|
"""
|
||||||
Provide a temporary directory with a mambaforge conda environment
|
Provide a temporary directory with a conda environment
|
||||||
"""
|
"""
|
||||||
installer_url, checksum = installer._mambaforge_url()
|
installer_url, checksum = installer._miniforge_url()
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
with conda.download_miniconda_installer(
|
with conda.download_miniconda_installer(
|
||||||
installer_url, checksum
|
installer_url, checksum
|
||||||
@@ -33,6 +34,15 @@ def test_ensure_packages(prefix):
|
|||||||
subprocess.check_call([os.path.join(prefix, "bin", "python"), "-c", "import numpy"])
|
subprocess.check_call([os.path.join(prefix, "bin", "python"), "-c", "import numpy"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_ensure_channel_packages(prefix):
|
||||||
|
"""
|
||||||
|
Test installing packages in conda environment
|
||||||
|
"""
|
||||||
|
conda.ensure_conda_packages(prefix, ["csvtk"], channels=("conda-forge", "bioconda"))
|
||||||
|
# Throws an error if this fails
|
||||||
|
subprocess.check_call([os.path.join(prefix, "bin", "csvtk"), "cat", "--help"])
|
||||||
|
|
||||||
|
|
||||||
def test_ensure_pip_packages(prefix):
|
def test_ensure_pip_packages(prefix):
|
||||||
"""
|
"""
|
||||||
Test installing pip packages in conda environment
|
Test installing pip packages in conda environment
|
||||||
|
|||||||
@@ -143,21 +143,24 @@ def test_remove_from_config_error():
|
|||||||
|
|
||||||
|
|
||||||
def test_reload_hub():
|
def test_reload_hub():
|
||||||
with mock.patch("tljh.systemd.restart_service") as restart_service, mock.patch(
|
with (
|
||||||
"tljh.systemd.check_service_active"
|
mock.patch("tljh.systemd.restart_service") as restart_service,
|
||||||
) as check_active, mock.patch("tljh.config.check_hub_ready") as check_ready:
|
mock.patch("tljh.systemd.check_service_active") as check_active,
|
||||||
|
mock.patch("tljh.config.check_hub_ready") as check_ready,
|
||||||
|
):
|
||||||
config.reload_component("hub")
|
config.reload_component("hub")
|
||||||
assert restart_service.called_with("jupyterhub")
|
restart_service.assert_called_with("jupyterhub")
|
||||||
assert check_active.called_with("jupyterhub")
|
check_active.assert_called_with("jupyterhub")
|
||||||
|
|
||||||
|
|
||||||
def test_reload_proxy(tljh_dir):
|
def test_reload_proxy(tljh_dir):
|
||||||
with mock.patch("tljh.systemd.restart_service") as restart_service, mock.patch(
|
with (
|
||||||
"tljh.systemd.check_service_active"
|
mock.patch("tljh.systemd.restart_service") as restart_service,
|
||||||
) as check_active:
|
mock.patch("tljh.systemd.check_service_active") as check_active,
|
||||||
|
):
|
||||||
config.reload_component("proxy")
|
config.reload_component("proxy")
|
||||||
assert restart_service.called_with("traefik")
|
restart_service.assert_called_with("traefik")
|
||||||
assert check_active.called_with("traefik")
|
check_active.assert_called_with("traefik")
|
||||||
assert os.path.exists(os.path.join(config.STATE_DIR, "traefik.toml"))
|
assert os.path.exists(os.path.join(config.STATE_DIR, "traefik.toml"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,18 +58,17 @@ def test_app_default():
|
|||||||
Test default application with no config overrides.
|
Test default application with no config overrides.
|
||||||
"""
|
"""
|
||||||
c = apply_mock_config({})
|
c = apply_mock_config({})
|
||||||
# default_url is not set, so JupyterHub will pick default.
|
|
||||||
assert "default_url" not in c.Spawner
|
|
||||||
|
|
||||||
|
|
||||||
def test_app_jupyterlab():
|
|
||||||
"""
|
|
||||||
Test setting JupyterLab as default application
|
|
||||||
"""
|
|
||||||
c = apply_mock_config({"user_environment": {"default_app": "jupyterlab"}})
|
|
||||||
assert c.Spawner.default_url == "/lab"
|
assert c.Spawner.default_url == "/lab"
|
||||||
|
|
||||||
|
|
||||||
|
def test_app_classic():
|
||||||
|
"""
|
||||||
|
Test setting classic as default application
|
||||||
|
"""
|
||||||
|
c = apply_mock_config({"user_environment": {"default_app": "classic"}})
|
||||||
|
assert c.Spawner.default_url == "/tree"
|
||||||
|
|
||||||
|
|
||||||
def test_auth_default():
|
def test_auth_default():
|
||||||
"""
|
"""
|
||||||
Test default authentication settings with no overrides
|
Test default authentication settings with no overrides
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Unit test functions in installer.py
|
Unit test functions in installer.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from subprocess import PIPE, run
|
from subprocess import PIPE, run
|
||||||
@@ -45,12 +46,12 @@ def test_ensure_admins(tljh_dir, admins, expected_config):
|
|||||||
|
|
||||||
|
|
||||||
def setup_conda(distro, version, prefix):
|
def setup_conda(distro, version, prefix):
|
||||||
"""Install mambaforge or miniconda in a prefix"""
|
"""Install miniforge or miniconda in a prefix"""
|
||||||
if distro == "mambaforge":
|
if distro == "mambaforge":
|
||||||
installer_url, _ = installer._mambaforge_url(version)
|
installer_url, _ = installer._miniforge_url(version)
|
||||||
|
installer_url = installer_url.replace("Miniforge3", "Mambaforge")
|
||||||
elif distro == "miniforge":
|
elif distro == "miniforge":
|
||||||
installer_url, _ = installer._mambaforge_url(version)
|
installer_url, _ = installer._miniforge_url(version)
|
||||||
installer_url = installer_url.replace("Mambaforge", "Miniforge3")
|
|
||||||
elif distro == "miniconda":
|
elif distro == "miniconda":
|
||||||
arch = os.uname().machine
|
arch = os.uname().machine
|
||||||
installer_url = (
|
installer_url = (
|
||||||
@@ -123,9 +124,9 @@ def _specifier(version):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"python": "3.10.*",
|
"python": "3.12.*",
|
||||||
"conda": "23.1.0",
|
"conda": "24.7.1",
|
||||||
"mamba": "1.4.1",
|
"mamba": "1.5.9",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# previous install, 1.0
|
# previous install, 1.0
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Unit test functions in installer.py
|
Unit test functions in installer.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Test functions for normalizing various kinds of values
|
Test functions for normalizing various kinds of values
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from tljh.normalize import generate_system_username
|
from tljh.normalize import generate_system_username
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Test traefik configuration"""
|
"""Test traefik configuration"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Test wrappers in tljw.user module
|
Test wrappers in tljw.user module
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import grp
|
import grp
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Utilities for working with the apt package manager
|
Utilities for working with the apt package manager
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Wrap conda commandline program
|
Wrap conda commandline program
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
@@ -98,9 +99,11 @@ def install_miniconda(installer_path, prefix):
|
|||||||
fix_permissions(prefix)
|
fix_permissions(prefix)
|
||||||
|
|
||||||
|
|
||||||
def ensure_conda_packages(prefix, packages, force_reinstall=False):
|
def ensure_conda_packages(
|
||||||
|
prefix, packages, channels=("conda-forge",), force_reinstall=False
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Ensure packages (from conda-forge) are installed in the conda prefix.
|
Ensure packages (from channels) are installed in the conda prefix.
|
||||||
|
|
||||||
Note that conda seem to update dependencies by default, so there is probably
|
Note that conda seem to update dependencies by default, so there is probably
|
||||||
no need to have a update parameter exposed for this function.
|
no need to have a update parameter exposed for this function.
|
||||||
@@ -117,13 +120,14 @@ def ensure_conda_packages(prefix, packages, force_reinstall=False):
|
|||||||
# avoids problems with RemoveError upgrading conda from old versions
|
# avoids problems with RemoveError upgrading conda from old versions
|
||||||
cmd += ["--force-reinstall"]
|
cmd += ["--force-reinstall"]
|
||||||
|
|
||||||
|
for channel in channels:
|
||||||
|
cmd += ["-c", channel]
|
||||||
|
|
||||||
abspath = os.path.abspath(prefix)
|
abspath = os.path.abspath(prefix)
|
||||||
|
|
||||||
utils.run_subprocess(
|
utils.run_subprocess(
|
||||||
cmd
|
cmd
|
||||||
+ [
|
+ [
|
||||||
"-c",
|
|
||||||
"conda-forge", # Make customizable if we ever need to
|
|
||||||
"--prefix",
|
"--prefix",
|
||||||
abspath,
|
abspath,
|
||||||
]
|
]
|
||||||
|
|||||||
158
tljh/config.py
158
tljh/config.py
@@ -18,9 +18,11 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
|
from contextlib import contextmanager
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from filelock import FileLock, Timeout
|
||||||
|
|
||||||
from .yaml import yaml
|
from .yaml import yaml
|
||||||
|
|
||||||
@@ -32,6 +34,22 @@ CONFIG_DIR = os.path.join(INSTALL_PREFIX, "config")
|
|||||||
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yaml")
|
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def config_file_lock(config_path, timeout=1):
|
||||||
|
"""Context manager to acquire the config file lock"""
|
||||||
|
lock_file = f"{config_path}.lock"
|
||||||
|
try:
|
||||||
|
with FileLock(lock_file).acquire(timeout=timeout):
|
||||||
|
yield
|
||||||
|
|
||||||
|
except Timeout:
|
||||||
|
print(
|
||||||
|
f"Another instance of tljh-config holds the lock {lock_file}.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def set_item_in_config(config, property_path, value):
|
def set_item_in_config(config, property_path, value):
|
||||||
"""
|
"""
|
||||||
Set key at property_path to value in config & return new config.
|
Set key at property_path to value in config & return new config.
|
||||||
@@ -154,92 +172,102 @@ def remove_item_from_config(config, property_path, value):
|
|||||||
return config_copy
|
return config_copy
|
||||||
|
|
||||||
|
|
||||||
|
def validate_config(config, validate):
|
||||||
|
"""
|
||||||
|
Validate changes to the config with tljh-config against the schema
|
||||||
|
"""
|
||||||
|
import jsonschema
|
||||||
|
|
||||||
|
from .config_schema import config_schema
|
||||||
|
|
||||||
|
try:
|
||||||
|
jsonschema.validate(instance=config, schema=config_schema)
|
||||||
|
except jsonschema.exceptions.ValidationError as e:
|
||||||
|
if validate:
|
||||||
|
print(
|
||||||
|
f"Config validation error: {e.message}.\n"
|
||||||
|
"You can still apply this change without validation by re-running your command with the --no-validate flag.\n"
|
||||||
|
"If you think this validation error is incorrect, please report it to https://github.com/jupyterhub/the-littlest-jupyterhub/issues.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def show_config(config_path):
|
def show_config(config_path):
|
||||||
"""
|
"""
|
||||||
Pretty print config from given config_path
|
Pretty print config from given config_path
|
||||||
"""
|
"""
|
||||||
try:
|
config = get_current_config(config_path)
|
||||||
with open(config_path) as f:
|
|
||||||
config = yaml.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
yaml.dump(config, sys.stdout)
|
yaml.dump(config, sys.stdout)
|
||||||
|
|
||||||
|
|
||||||
def set_config_value(config_path, key_path, value):
|
def set_config_value(config_path, key_path, value, validate=True):
|
||||||
"""
|
"""
|
||||||
Set key at key_path in config_path to value
|
Set key at key_path in config_path to value
|
||||||
"""
|
"""
|
||||||
# FIXME: Have a file lock here
|
with config_file_lock(config_path):
|
||||||
# FIXME: Validate schema here
|
config = get_current_config(config_path)
|
||||||
try:
|
config = set_item_in_config(config, key_path, value)
|
||||||
with open(config_path) as f:
|
validate_config(config, validate)
|
||||||
config = yaml.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config = set_item_in_config(config, key_path, value)
|
with open(config_path, "w") as f:
|
||||||
|
yaml.dump(config, f)
|
||||||
with open(config_path, "w") as f:
|
|
||||||
yaml.dump(config, f)
|
|
||||||
|
|
||||||
|
|
||||||
def unset_config_value(config_path, key_path):
|
def unset_config_value(config_path, key_path, validate=True):
|
||||||
"""
|
"""
|
||||||
Unset key at key_path in config_path
|
Unset key at key_path in config_path
|
||||||
"""
|
"""
|
||||||
# FIXME: Have a file lock here
|
with config_file_lock(config_path):
|
||||||
# FIXME: Validate schema here
|
config = get_current_config(config_path)
|
||||||
try:
|
config = unset_item_from_config(config, key_path)
|
||||||
with open(config_path) as f:
|
validate_config(config, validate)
|
||||||
config = yaml.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config = unset_item_from_config(config, key_path)
|
with open(config_path, "w") as f:
|
||||||
|
yaml.dump(config, f)
|
||||||
with open(config_path, "w") as f:
|
|
||||||
yaml.dump(config, f)
|
|
||||||
|
|
||||||
|
|
||||||
def add_config_value(config_path, key_path, value):
|
def add_config_value(config_path, key_path, value, validate=True):
|
||||||
"""
|
"""
|
||||||
Add value to list at key_path
|
Add value to list at key_path
|
||||||
"""
|
"""
|
||||||
# FIXME: Have a file lock here
|
with config_file_lock(config_path):
|
||||||
# FIXME: Validate schema here
|
config = get_current_config(config_path)
|
||||||
try:
|
config = add_item_to_config(config, key_path, value)
|
||||||
with open(config_path) as f:
|
validate_config(config, validate)
|
||||||
config = yaml.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config = add_item_to_config(config, key_path, value)
|
with open(config_path, "w") as f:
|
||||||
|
yaml.dump(config, f)
|
||||||
with open(config_path, "w") as f:
|
|
||||||
yaml.dump(config, f)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_config_value(config_path, key_path, value):
|
def remove_config_value(config_path, key_path, value, validate=True):
|
||||||
"""
|
"""
|
||||||
Remove value from list at key_path
|
Remove value from list at key_path
|
||||||
"""
|
"""
|
||||||
# FIXME: Have a file lock here
|
with config_file_lock(config_path):
|
||||||
# FIXME: Validate schema here
|
config = get_current_config(config_path)
|
||||||
|
config = remove_item_from_config(config, key_path, value)
|
||||||
|
validate_config(config, validate)
|
||||||
|
|
||||||
|
with open(config_path, "w") as f:
|
||||||
|
yaml.dump(config, f)
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_config(config_path):
|
||||||
|
"""
|
||||||
|
Retrieve the current config at config_path
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
with open(config_path) as f:
|
with open(config_path) as f:
|
||||||
config = yaml.load(f)
|
return yaml.load(f)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
config = {}
|
return {}
|
||||||
|
|
||||||
config = remove_item_from_config(config, key_path, value)
|
|
||||||
|
|
||||||
with open(config_path, "w") as f:
|
|
||||||
yaml.dump(config, f)
|
|
||||||
|
|
||||||
|
|
||||||
def check_hub_ready():
|
def check_hub_ready():
|
||||||
|
"""
|
||||||
|
Checks that hub is running.
|
||||||
|
"""
|
||||||
from .configurer import load_config
|
from .configurer import load_config
|
||||||
|
|
||||||
base_url = load_config()["base_url"]
|
base_url = load_config()["base_url"]
|
||||||
@@ -336,6 +364,18 @@ def main(argv=None):
|
|||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
"--config-path", default=CONFIG_FILE, help="Path to TLJH config.yaml file"
|
"--config-path", default=CONFIG_FILE, help="Path to TLJH config.yaml file"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
argparser.add_argument(
|
||||||
|
"--validate", action="store_true", help="Validate the TLJH config"
|
||||||
|
)
|
||||||
|
argparser.add_argument(
|
||||||
|
"--no-validate",
|
||||||
|
dest="validate",
|
||||||
|
action="store_false",
|
||||||
|
help="Do not validate the TLJH config",
|
||||||
|
)
|
||||||
|
argparser.set_defaults(validate=True)
|
||||||
|
|
||||||
subparsers = argparser.add_subparsers(dest="action")
|
subparsers = argparser.add_subparsers(dest="action")
|
||||||
|
|
||||||
show_parser = subparsers.add_parser("show", help="Show current configuration")
|
show_parser = subparsers.add_parser("show", help="Show current configuration")
|
||||||
@@ -383,13 +423,19 @@ def main(argv=None):
|
|||||||
if args.action == "show":
|
if args.action == "show":
|
||||||
show_config(args.config_path)
|
show_config(args.config_path)
|
||||||
elif args.action == "set":
|
elif args.action == "set":
|
||||||
set_config_value(args.config_path, args.key_path, parse_value(args.value))
|
set_config_value(
|
||||||
|
args.config_path, args.key_path, parse_value(args.value), args.validate
|
||||||
|
)
|
||||||
elif args.action == "unset":
|
elif args.action == "unset":
|
||||||
unset_config_value(args.config_path, args.key_path)
|
unset_config_value(args.config_path, args.key_path, args.validate)
|
||||||
elif args.action == "add-item":
|
elif args.action == "add-item":
|
||||||
add_config_value(args.config_path, args.key_path, parse_value(args.value))
|
add_config_value(
|
||||||
|
args.config_path, args.key_path, parse_value(args.value), args.validate
|
||||||
|
)
|
||||||
elif args.action == "remove-item":
|
elif args.action == "remove-item":
|
||||||
remove_config_value(args.config_path, args.key_path, parse_value(args.value))
|
remove_config_value(
|
||||||
|
args.config_path, args.key_path, parse_value(args.value), args.validate
|
||||||
|
)
|
||||||
elif args.action == "reload":
|
elif args.action == "reload":
|
||||||
reload_component(args.component)
|
reload_component(args.component)
|
||||||
else:
|
else:
|
||||||
|
|||||||
117
tljh/config_schema.py
Normal file
117
tljh/config_schema.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
"""
|
||||||
|
The schema against which the TLJH config file can be validated.
|
||||||
|
|
||||||
|
Validation occurs when changing values with tljh-config.
|
||||||
|
"""
|
||||||
|
|
||||||
|
config_schema = {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Littlest JupyterHub YAML config file",
|
||||||
|
"definitions": {
|
||||||
|
"BaseURL": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"Users": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"extra_user_groups": {"type": "object", "items": {"type": "string"}},
|
||||||
|
"allowed": {"type": "array", "items": {"type": "string"}},
|
||||||
|
"banned": {"type": "array", "items": {"type": "string"}},
|
||||||
|
"admin": {"type": "array", "items": {"type": "string"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Services": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cull": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {"type": "boolean"},
|
||||||
|
"timeout": {"type": "integer"},
|
||||||
|
"every": {"type": "integer"},
|
||||||
|
"concurrency": {"type": "integer"},
|
||||||
|
"users": {"type": "boolean"},
|
||||||
|
"max_age": {"type": "integer"},
|
||||||
|
"remove_named_servers": {"type": "boolean"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"HTTP": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"address": {"type": "string", "format": "ipv4"},
|
||||||
|
"port": {"type": "integer"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"HTTPS": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {"type": "boolean"},
|
||||||
|
"address": {"type": "string", "format": "ipv4"},
|
||||||
|
"port": {"type": "integer"},
|
||||||
|
"tls": {"$ref": "#/definitions/TLS"},
|
||||||
|
"letsencrypt": {"$ref": "#/definitions/LetsEncrypt"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"LetsEncrypt": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"email": {"type": "string", "format": "email"},
|
||||||
|
"domains": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string", "format": "hostname"},
|
||||||
|
},
|
||||||
|
"staging": {"type": "boolean"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"TLS": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {"key": {"type": "string"}, "cert": {"type": "string"}},
|
||||||
|
},
|
||||||
|
"Limits": {
|
||||||
|
"description": "User CPU and memory limits.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {"memory": {"type": "string"}, "cpu": {"type": "integer"}},
|
||||||
|
},
|
||||||
|
"UserEnvironment": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"default_app": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["jupyterlab", "classic"],
|
||||||
|
"default": "jupyterlab",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"TraefikAPI": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"ip": {"type": "string", "format": "ipv4"},
|
||||||
|
"port": {"type": "integer"},
|
||||||
|
"username": {"type": "string"},
|
||||||
|
"password": {"type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"additionalProperties": False,
|
||||||
|
"base_url": {"$ref": "#/definitions/BaseURL"},
|
||||||
|
"user_environment": {"$ref": "#/definitions/UserEnvironment"},
|
||||||
|
"users": {"$ref": "#/definitions/Users"},
|
||||||
|
"limits": {"$ref": "#/definitions/Limits"},
|
||||||
|
"https": {"$ref": "#/definitions/HTTPS"},
|
||||||
|
"http": {"$ref": "#/definitions/HTTP"},
|
||||||
|
"traefik_api": {"$ref": "#/definitions/TraefikAPI"},
|
||||||
|
"services": {"$ref": "#/definitions/Services"},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -52,7 +52,7 @@ default = {
|
|||||||
"password": "",
|
"password": "",
|
||||||
},
|
},
|
||||||
"user_environment": {
|
"user_environment": {
|
||||||
"default_app": "classic",
|
"default_app": "jupyterlab",
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"cull": {
|
"cull": {
|
||||||
@@ -199,6 +199,14 @@ def update_userlists(c, config):
|
|||||||
"""
|
"""
|
||||||
users = config["users"]
|
users = config["users"]
|
||||||
|
|
||||||
|
if (
|
||||||
|
not users["allowed"]
|
||||||
|
and config["auth"]["type"] == default["auth"]["type"]
|
||||||
|
and "allow_all" not in c.FirstUseAuthenticator
|
||||||
|
):
|
||||||
|
# _default_ authenticator, enable allow_all if no users specified
|
||||||
|
c.FirstUseAuthenticator.allow_all = True
|
||||||
|
|
||||||
c.Authenticator.allowed_users = set(users["allowed"])
|
c.Authenticator.allowed_users = set(users["allowed"])
|
||||||
c.Authenticator.blocked_users = set(users["banned"])
|
c.Authenticator.blocked_users = set(users["banned"])
|
||||||
c.Authenticator.admin_users = set(users["admin"])
|
c.Authenticator.admin_users = set(users["admin"])
|
||||||
@@ -231,6 +239,8 @@ def update_user_environment(c, config):
|
|||||||
# Set default application users are launched into
|
# Set default application users are launched into
|
||||||
if user_env["default_app"] == "jupyterlab":
|
if user_env["default_app"] == "jupyterlab":
|
||||||
c.Spawner.default_url = "/lab"
|
c.Spawner.default_url = "/lab"
|
||||||
|
elif user_env["default_app"] == "classic":
|
||||||
|
c.Spawner.default_url = "/tree"
|
||||||
|
|
||||||
|
|
||||||
def update_user_account_config(c, config):
|
def update_user_account_config(c, config):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Hook specifications that pluggy plugins can override
|
Hook specifications that pluggy plugins can override
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
|
|
||||||
hookspec = pluggy.HookspecMarker("tljh")
|
hookspec = pluggy.HookspecMarker("tljh")
|
||||||
@@ -14,6 +15,13 @@ def tljh_extra_user_conda_packages():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@hookspec
|
||||||
|
def tljh_extra_user_conda_channels():
|
||||||
|
"""
|
||||||
|
Return a list of conda channels to be used during user environment installation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@hookspec
|
@hookspec
|
||||||
def tljh_extra_user_pip_packages():
|
def tljh_extra_user_pip_packages():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -115,7 +115,6 @@ def ensure_jupyterhub_package(prefix):
|
|||||||
os.path.join(HERE, "requirements-hub-env.txt"),
|
os.path.join(HERE, "requirements-hub-env.txt"),
|
||||||
upgrade=True,
|
upgrade=True,
|
||||||
)
|
)
|
||||||
traefik.ensure_traefik_binary(prefix)
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_usergroups():
|
def ensure_usergroups():
|
||||||
@@ -136,13 +135,13 @@ def ensure_usergroups():
|
|||||||
f.write("Defaults exempt_group = jupyterhub-admins\n")
|
f.write("Defaults exempt_group = jupyterhub-admins\n")
|
||||||
|
|
||||||
|
|
||||||
# Install mambaforge using an installer from
|
# Install miniforge using an installer from
|
||||||
# https://github.com/conda-forge/miniforge/releases
|
# https://github.com/conda-forge/miniforge/releases
|
||||||
MAMBAFORGE_VERSION = "23.1.0-1"
|
MINIFORGE_VERSION = "24.7.1-2"
|
||||||
# sha256 checksums
|
# sha256 checksums
|
||||||
MAMBAFORGE_CHECKSUMS = {
|
MINIFORGE_CHECKSUMS = {
|
||||||
"aarch64": "d9d89c9e349369702171008d9ee7c5ce80ed420e5af60bd150a3db4bf674443a",
|
"aarch64": "7bf60bce50f57af7ea4500b45eeb401d9350011ab34c9c45f736647d8dba9021",
|
||||||
"x86_64": "cfb16c47dc2d115c8b114280aa605e322173f029fdb847a45348bf4bd23c62ab",
|
"x86_64": "636f7faca2d51ee42b4640ce160c751a46d57621ef4bf14378704c87c5db4fe3",
|
||||||
}
|
}
|
||||||
|
|
||||||
# minimum versions of packages
|
# minimum versions of packages
|
||||||
@@ -156,22 +155,22 @@ MINIMUM_VERSIONS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _mambaforge_url(version=MAMBAFORGE_VERSION, arch=None):
|
def _miniforge_url(version=MINIFORGE_VERSION, arch=None):
|
||||||
"""Return (URL, checksum) for mambaforge download for a given version and arch
|
"""Return (URL, checksum) for miniforge download for a given version and arch
|
||||||
|
|
||||||
Default values provided for both version and arch
|
Default values provided for both version and arch
|
||||||
"""
|
"""
|
||||||
if arch is None:
|
if arch is None:
|
||||||
arch = os.uname().machine
|
arch = os.uname().machine
|
||||||
installer_url = "https://github.com/conda-forge/miniforge/releases/download/{v}/Mambaforge-{v}-Linux-{arch}.sh".format(
|
installer_url = "https://github.com/conda-forge/miniforge/releases/download/{v}/Miniforge3-{v}-Linux-{arch}.sh".format(
|
||||||
v=version,
|
v=version,
|
||||||
arch=arch,
|
arch=arch,
|
||||||
)
|
)
|
||||||
# Check system architecture, set appropriate installer checksum
|
# Check system architecture, set appropriate installer checksum
|
||||||
checksum = MAMBAFORGE_CHECKSUMS.get(arch)
|
checksum = MINIFORGE_CHECKSUMS.get(arch)
|
||||||
if not checksum:
|
if not checksum:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unsupported architecture: {arch}. TLJH only supports {','.join(MAMBAFORGE_CHECKSUMS.keys())}"
|
f"Unsupported architecture: {arch}. TLJH only supports {','.join(MINIFORGE_CHECKSUMS.keys())}"
|
||||||
)
|
)
|
||||||
return installer_url, checksum
|
return installer_url, checksum
|
||||||
|
|
||||||
@@ -198,7 +197,7 @@ def ensure_user_environment(user_requirements_txt_file):
|
|||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
logger.info("Downloading & setting up user environment...")
|
logger.info("Downloading & setting up user environment...")
|
||||||
installer_url, installer_sha256 = _mambaforge_url()
|
installer_url, installer_sha256 = _miniforge_url()
|
||||||
with conda.download_miniconda_installer(
|
with conda.download_miniconda_installer(
|
||||||
installer_url, installer_sha256
|
installer_url, installer_sha256
|
||||||
) as installer_path:
|
) as installer_path:
|
||||||
@@ -242,11 +241,10 @@ def ensure_user_environment(user_requirements_txt_file):
|
|||||||
)
|
)
|
||||||
to_upgrade.append(pkg)
|
to_upgrade.append(pkg)
|
||||||
|
|
||||||
# force reinstall conda/mamba to ensure a basically consistent env
|
# force reinstall conda/mamba to ensure conda doesn't raise error
|
||||||
# avoids issues with RemoveError: 'requests' is a dependency of conda
|
# "RemoveError: 'requests' is a dependency of conda" later on when
|
||||||
# only do this for 'old' conda versions known to have a problem
|
# conda/mamba is used to install/upgrade something
|
||||||
# we don't know how old, but we know 4.10 is affected and 23.1 is not
|
if not is_fresh_install:
|
||||||
if not is_fresh_install and V(package_versions.get("conda", "0")) < V("23.1"):
|
|
||||||
# force-reinstall doesn't upgrade packages
|
# force-reinstall doesn't upgrade packages
|
||||||
# it reinstalls them in-place
|
# it reinstalls them in-place
|
||||||
# only reinstall packages already present
|
# only reinstall packages already present
|
||||||
@@ -450,13 +448,18 @@ def run_plugin_actions(plugin_manager):
|
|||||||
|
|
||||||
# Install conda packages
|
# Install conda packages
|
||||||
conda_packages = list(set(itertools.chain(*hook.tljh_extra_user_conda_packages())))
|
conda_packages = list(set(itertools.chain(*hook.tljh_extra_user_conda_packages())))
|
||||||
|
conda_channels = list(itertools.chain(*hook.tljh_extra_user_conda_channels()))
|
||||||
|
if len(conda_channels) == 0:
|
||||||
|
conda_channels = ("conda-forge",)
|
||||||
if conda_packages:
|
if conda_packages:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Installing {} user conda packages collected from plugins: {}".format(
|
"Installing {} user conda packages collected from plugins: {}".format(
|
||||||
len(conda_packages), " ".join(conda_packages)
|
len(conda_packages), " ".join(conda_packages)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
conda.ensure_conda_packages(USER_ENV_PREFIX, conda_packages)
|
conda.ensure_conda_packages(
|
||||||
|
USER_ENV_PREFIX, conda_packages, channels=conda_channels
|
||||||
|
)
|
||||||
|
|
||||||
# Install pip packages
|
# Install pip packages
|
||||||
user_pip_packages = list(set(itertools.chain(*hook.tljh_extra_user_pip_packages())))
|
user_pip_packages = list(set(itertools.chain(*hook.tljh_extra_user_pip_packages())))
|
||||||
@@ -532,6 +535,7 @@ def main():
|
|||||||
|
|
||||||
logger.info("Setting up JupyterHub...")
|
logger.info("Setting up JupyterHub...")
|
||||||
ensure_jupyterhub_package(HUB_ENV_PREFIX)
|
ensure_jupyterhub_package(HUB_ENV_PREFIX)
|
||||||
|
traefik.ensure_traefik_binary(HUB_ENV_PREFIX)
|
||||||
|
|
||||||
# Stop the http server with the progress page before traefik starts
|
# Stop the http server with the progress page before traefik starts
|
||||||
if args.progress_page_server_pid:
|
if args.progress_page_server_pid:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Setup tljh logging"""
|
"""Setup tljh logging"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Functions to normalize various inputs
|
Functions to normalize various inputs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
# If a dependency is bumped to a new major version, we should make a major
|
# If a dependency is bumped to a new major version, we should make a major
|
||||||
# version release of tljh.
|
# version release of tljh.
|
||||||
#
|
#
|
||||||
jupyterhub>=4.0.1,<5
|
jupyterhub>=5.1.0,<6
|
||||||
jupyterhub-systemdspawner>=1.0.1,<2
|
jupyterhub-systemdspawner>=1.0.1,<2
|
||||||
jupyterhub-firstuseauthenticator>=1.0.0,<2
|
jupyterhub-firstuseauthenticator>=1.1.0,<2
|
||||||
jupyterhub-nativeauthenticator>=1.2.0,<2
|
jupyterhub-nativeauthenticator>=1.3.0,<2
|
||||||
jupyterhub-ldapauthenticator>=1.3.2,<2
|
jupyterhub-ldapauthenticator==2.0.0b2 # FIXME: update to >=2.0.0,<3
|
||||||
jupyterhub-tmpauthenticator>=1.0.0,<2
|
jupyterhub-tmpauthenticator>=1.0.0,<2
|
||||||
oauthenticator>=16.0.2,<17
|
oauthenticator>=17,<18
|
||||||
jupyterhub-idle-culler>=1.2.1,<2
|
jupyterhub-idle-culler>=1.4.0,<2
|
||||||
|
|
||||||
# pycurl is installed to improve reliability and performance for when JupyterHub
|
# pycurl is installed to improve reliability and performance for when JupyterHub
|
||||||
# makes web requests. JupyterHub will use tornado's CurlAsyncHTTPClient when
|
# makes web requests. JupyterHub will use tornado's CurlAsyncHTTPClient when
|
||||||
@@ -25,4 +25,4 @@ jupyterhub-idle-culler>=1.2.1,<2
|
|||||||
# ref: https://www.tornadoweb.org/en/stable/httpclient.html#module-tornado.simple_httpclient
|
# ref: https://www.tornadoweb.org/en/stable/httpclient.html#module-tornado.simple_httpclient
|
||||||
# ref: https://github.com/jupyterhub/the-littlest-jupyterhub/issues/289
|
# ref: https://github.com/jupyterhub/the-littlest-jupyterhub/issues/289
|
||||||
#
|
#
|
||||||
pycurl>=7.45.2,<8
|
pycurl>=7.45.3,<8
|
||||||
|
|||||||
@@ -8,11 +8,20 @@
|
|||||||
# the requirements-txt-fixer pre-commit hook that sorted them and made
|
# the requirements-txt-fixer pre-commit hook that sorted them and made
|
||||||
# our integration tests fail.
|
# our integration tests fail.
|
||||||
#
|
#
|
||||||
notebook==6.*
|
# ref: https://github.com/jupyter/notebook
|
||||||
jupyterlab==3.*
|
notebook>=7.2.2,<8
|
||||||
|
|
||||||
|
# ref: https://github.com/jupyterlab/jupyterlab
|
||||||
|
jupyterlab>=4.2.5,<5
|
||||||
|
|
||||||
# nbgitpuller for easily pulling in Git repositories
|
# nbgitpuller for easily pulling in Git repositories
|
||||||
nbgitpuller==1.*
|
# ref: https://github.com/jupyterhub/nbgitpuller
|
||||||
|
nbgitpuller>=1.2.1,<2
|
||||||
|
|
||||||
# jupyter-resource-usage to show people how much RAM they are using
|
# jupyter-resource-usage to show people how much RAM they are using
|
||||||
jupyter-resource-usage==0.7.*
|
# ref: https://github.com/jupyter-server/jupyter-resource-usage
|
||||||
|
jupyter-resource-usage>=1.1.0,<2
|
||||||
|
|
||||||
# Most people consider ipywidgets to be part of the core notebook experience
|
# Most people consider ipywidgets to be part of the core notebook experience
|
||||||
ipywidgets==8.*
|
# ref: https://github.com/jupyter-widgets/ipywidgets
|
||||||
|
ipywidgets>=8.1.5,<9
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Environment=TLJH_INSTALL_PREFIX={install_prefix}
|
|||||||
Environment=PATH={install_prefix}/hub/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
Environment=PATH={install_prefix}/hub/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
# Run upgrade-db before starting, in case Hub version has changed
|
# Run upgrade-db before starting, in case Hub version has changed
|
||||||
# This is a no-op when no db exists or no upgrades are needed
|
# This is a no-op when no db exists or no upgrades are needed
|
||||||
ExecStart={python_interpreter_path} -m jupyterhub.app -f {jupyterhub_config_path} --upgrade-db
|
ExecStart={python_interpreter_path} -m jupyterhub -f {jupyterhub_config_path} --upgrade-db
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
# Start service when system boots
|
# Start service when system boots
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Wraps systemctl to install, uninstall, start & stop systemd services.
|
|||||||
|
|
||||||
If we use a debian package instead, we can get rid of all this code.
|
If we use a debian package instead, we can get rid of all this code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Traefik installation and setup"""
|
"""Traefik installation and setup"""
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
@@ -28,13 +29,13 @@ else:
|
|||||||
plat = None
|
plat = None
|
||||||
|
|
||||||
# Traefik releases: https://github.com/traefik/traefik/releases
|
# Traefik releases: https://github.com/traefik/traefik/releases
|
||||||
traefik_version = "2.10.1"
|
traefik_version = "3.1.4"
|
||||||
|
|
||||||
# record sha256 hashes for supported platforms here
|
# record sha256 hashes for supported platforms here
|
||||||
# checksums are published in the checksums.txt of each release
|
# checksums are published in the checksums.txt of each release
|
||||||
checksums = {
|
checksums = {
|
||||||
"linux_amd64": "8d9bce0e6a5bf40b5399dbb1d5e3e5c57b9f9f04dd56a2dd57cb0713130bc824",
|
"linux_amd64": "eb7227b1b235195355904839c514a9ed6a0aecdcf5dab02ad48db21b05c5e700",
|
||||||
"linux_arm64": "260a574105e44901f8c9c562055936d81fbd9c96a21daaa575502dc69bfe390a",
|
"linux_arm64": "e5d970a7f11267b70a8e308cb80f859bba4f420f24789f7393fdf3f4cd031631",
|
||||||
}
|
}
|
||||||
|
|
||||||
_tljh_path = Path(__file__).parent.resolve()
|
_tljh_path = Path(__file__).parent.resolve()
|
||||||
@@ -90,7 +91,10 @@ def check_traefik_version(traefik_bin):
|
|||||||
|
|
||||||
@backoff.on_exception(backoff.expo, Exception, max_tries=2, giveup=fatal_error)
|
@backoff.on_exception(backoff.expo, Exception, max_tries=2, giveup=fatal_error)
|
||||||
def ensure_traefik_binary(prefix):
|
def ensure_traefik_binary(prefix):
|
||||||
"""Download and install the traefik binary to a location identified by a prefix path such as '/opt/tljh/hub/'"""
|
"""
|
||||||
|
Ensure that a traefik binary of a hardcoded version is made available at a
|
||||||
|
prefix path such as '/opt/tljh/hub/'.
|
||||||
|
"""
|
||||||
if plat is None:
|
if plat is None:
|
||||||
raise OSError(
|
raise OSError(
|
||||||
f"Error. Platform: {os.uname().sysname} / {machine} Not supported."
|
f"Error. Platform: {os.uname().sysname} / {machine} Not supported."
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ User management for tljh.
|
|||||||
|
|
||||||
Supports minimal user & group management
|
Supports minimal user & group management
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import grp
|
import grp
|
||||||
import pwd
|
import pwd
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Miscellaneous functions useful in at least two places unrelated to each other
|
Miscellaneous functions useful in at least two places unrelated to each other
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
ensures the same yaml settings for reading/writing
|
ensures the same yaml settings for reading/writing
|
||||||
throughout tljh
|
throughout tljh
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
from ruamel.yaml.composer import Composer
|
from ruamel.yaml.composer import Composer
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user