mirror of
https://github.com/jupyterhub/the-littlest-jupyterhub.git
synced 2025-12-18 21:54:05 +08:00
Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a47a171850 | ||
|
|
04e2568fb3 | ||
|
|
145d58f86e | ||
|
|
3ebdbe1d83 | ||
|
|
f573525bf0 | ||
|
|
0370b57b32 | ||
|
|
cbe577e596 | ||
|
|
64790b4c95 | ||
|
|
d2b0fd2c6c | ||
|
|
e581232920 | ||
|
|
7514616e23 | ||
|
|
06d4670512 | ||
|
|
f4538ab06b | ||
|
|
bb17a1b980 | ||
|
|
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 | ||
|
|
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.10.1
|
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.7.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
|
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.1.0"
|
rev: "7.1.1"
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,6 @@ for information on the different ways of contributing to The Littlest JupyterHub
|
|||||||
See [this blog post](http://words.yuvi.in/post/the-littlest-jupyterhub/) for
|
See [this blog post](http://words.yuvi.in/post/the-littlest-jupyterhub/) for
|
||||||
more information.
|
more information.
|
||||||
|
|
||||||
## Development Status
|
|
||||||
|
|
||||||
This project is currently in **beta** state. Folks have been using installations
|
|
||||||
of TLJH for more than a year now to great success. While we try hard not to, we
|
|
||||||
might still make breaking changes that have no clear upgrade pathway.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
The Littlest JupyterHub (TLJH) can run on any server that is running at least
|
The Littlest JupyterHub (TLJH) can run on any server that is running at least
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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/)
|
||||||
|
|||||||
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 |
@@ -4,12 +4,6 @@ A simple [JupyterHub](https://github.com/jupyterhub/jupyterhub) distribution for
|
|||||||
a small (0-100) number of users on a single server. We recommend reading
|
a small (0-100) number of users on a single server. We recommend reading
|
||||||
[](/topic/whentouse) to determine if this is the right tool for you.
|
[](/topic/whentouse) to determine if this is the right tool for you.
|
||||||
|
|
||||||
## Development Status
|
|
||||||
|
|
||||||
This project is currently in **beta** state. Folks have been using installations
|
|
||||||
of TLJH for more than a year now to great success. While we try hard not to, we
|
|
||||||
might still make breaking changes that have no clear upgrade pathway.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
The Littlest JupyterHub (TLJH) can run on any server that is running **Debian 11** or **Ubuntu 20.04** or **22.04** on an amd64 or arm64 CPU architecture.
|
The Littlest JupyterHub (TLJH) can run on any server that is running **Debian 11** or **Ubuntu 20.04** or **22.04** on an amd64 or arm64 CPU architecture.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,6 +2,135 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2.0
|
||||||
|
|
||||||
|
### 2.0.0 - 2024-10-21
|
||||||
|
|
||||||
|
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.2.0,<6
|
||||||
|
- Refer to the [JupyterHub changelog] for details and pay attention to the
|
||||||
|
changelog entry for JupyterHub version 5.0.0.
|
||||||
|
- OAuthenticator 16.0.4 has been upgraded to >=17.1.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 changelog
|
||||||
|
entry for OAuthenticator 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 changelog entry 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.0/tljh/requirements-user-env-extras.txt
|
||||||
|
[tljh/requirements-hub-env.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/2.0.0/tljh/requirements-hub-env.txt
|
||||||
|
|
||||||
|
The changes in the respective environments between TLJH version 1.0.0 and 2.0.0
|
||||||
|
are summarized below.
|
||||||
|
|
||||||
|
| Dependency changes in the _hub environment_ | Version in 1.0.0 | Version in 2.0.0 | Changelog link | Note |
|
||||||
|
| ------------------------------------------------------------------------------ | ---------------- | ---------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||||
|
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | >=4.0.2,<5 | >=5.2.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.2,<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.1.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.0 | [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.0 | Version in fresh install of 2.0.0 | Changelog link | Note |
|
||||||
|
| -------------------------------------------------------- | ---------------- | --------------------------- | --------------------------------- | --------------------------------------------------------------------------------- | ------------------------ |
|
||||||
|
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | >=4.0.2,<5 | >=5.2.0,<6 | >=5.2.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
|
||||||
|
|
||||||
|
- hub env: avoid installing pycurl using wheel with an issue [#1006](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1006) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- Update jupyterhub pinning to >=5.2.0,<6 from ==5.1.0 [#1008](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1008) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- hub env: pin to jupyterhub 5.1.0 [#1007](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1007) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- hub env: bump to ldapauthenticator 2.0.0 and other lower bounds [#1004](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1004) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- Remove mention about beta state [#1009](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1009) ([@consideRatio](https://github.com/consideRatio))
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- Test upgrade from 1.\* [#1003](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1003) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||||
|
- 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-10-21&type=c))
|
||||||
|
|
||||||
|
@consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AconsideRatio+updated%3A2023-08-11..2024-10-21&type=Issues)) | @davidalber ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Adavidalber+updated%3A2023-08-11..2024-10-21&type=Issues)) | @josedaudi ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajosedaudi+updated%3A2023-08-11..2024-10-21&type=Issues)) | @jrdnbradford ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajrdnbradford+updated%3A2023-08-11..2024-10-21&type=Issues)) | @kiliansinger ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Akiliansinger+updated%3A2023-08-11..2024-10-21&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Amanics+updated%3A2023-08-11..2024-10-21&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aminrk+updated%3A2023-08-11..2024-10-21&type=Issues)) | @MridulS ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AMridulS+updated%3A2023-08-11..2024-10-21&type=Issues)) | @pdebuyl ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Apdebuyl+updated%3A2023-08-11..2024-10-21&type=Issues)) | @schwebke ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aschwebke+updated%3A2023-08-11..2024-10-21&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ayuvipanda+updated%3A2023-08-11..2024-10-21&type=Issues))
|
||||||
|
|
||||||
## 1.0
|
## 1.0
|
||||||
|
|
||||||
### 1.0.0 - 2023-08-11
|
### 1.0.0 - 2023-08-11
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ def test_labextensions():
|
|||||||
# jupyter-labextension 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-labextension", "list"],
|
["/opt/tljh/user/bin/jupyter-labextension", "list"],
|
||||||
stderr=subprocess.PIPE,
|
capture_output=True,
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
|
|||||||
@@ -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.0"
|
current = "2.0.0"
|
||||||
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.0",
|
version="2.0.0",
|
||||||
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"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
]
|
]
|
||||||
|
|||||||
138
tljh/config.py
138
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:
|
|
||||||
with open(config_path) as f:
|
|
||||||
config = yaml.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config = set_item_in_config(config, key_path, value)
|
config = set_item_in_config(config, key_path, value)
|
||||||
|
validate_config(config, validate)
|
||||||
|
|
||||||
with open(config_path, "w") as f:
|
with open(config_path, "w") as f:
|
||||||
yaml.dump(config, 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:
|
|
||||||
with open(config_path) as f:
|
|
||||||
config = yaml.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config = unset_item_from_config(config, key_path)
|
config = unset_item_from_config(config, key_path)
|
||||||
|
validate_config(config, validate)
|
||||||
|
|
||||||
with open(config_path, "w") as f:
|
with open(config_path, "w") as f:
|
||||||
yaml.dump(config, 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:
|
|
||||||
with open(config_path) as f:
|
|
||||||
config = yaml.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config = add_item_to_config(config, key_path, value)
|
config = add_item_to_config(config, key_path, value)
|
||||||
|
validate_config(config, validate)
|
||||||
|
|
||||||
with open(config_path, "w") as f:
|
with open(config_path, "w") as f:
|
||||||
yaml.dump(config, 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)
|
||||||
try:
|
|
||||||
with open(config_path) as f:
|
|
||||||
config = yaml.load(f)
|
|
||||||
except FileNotFoundError:
|
|
||||||
config = {}
|
|
||||||
|
|
||||||
config = remove_item_from_config(config, key_path, value)
|
config = remove_item_from_config(config, key_path, value)
|
||||||
|
validate_config(config, validate)
|
||||||
|
|
||||||
with open(config_path, "w") as f:
|
with open(config_path, "w") as f:
|
||||||
yaml.dump(config, f)
|
yaml.dump(config, f)
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_config(config_path):
|
||||||
|
"""
|
||||||
|
Retrieve the current config at config_path
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(config_path) as f:
|
||||||
|
return yaml.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
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"},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -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"])
|
||||||
|
|||||||
@@ -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.2,<5
|
jupyterhub>=5.2.0,<6
|
||||||
jupyterhub-systemdspawner>=1.0.1,<2
|
jupyterhub-systemdspawner>=1.0.2,<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.0,<3
|
||||||
jupyterhub-tmpauthenticator>=1.0.0,<2
|
jupyterhub-tmpauthenticator>=1.0.0,<2
|
||||||
oauthenticator>=16.0.4,<17
|
oauthenticator>=17.1.0,<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,7 @@ 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
|
# FIXME: pycurl is installed from source without its wheel for 7.45.3 has an
|
||||||
|
# issue reported in https://github.com/pycurl/pycurl/issues/834.
|
||||||
|
#
|
||||||
|
pycurl>=7.45.3,<8 --no-binary=pycurl
|
||||||
|
|||||||
@@ -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==7.*
|
# ref: https://github.com/jupyter/notebook
|
||||||
jupyterlab==4.*
|
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==1.*
|
# 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