mirror of
https://github.com/jupyterhub/the-littlest-jupyterhub.git
synced 2025-12-18 21:54:05 +08:00
Compare commits
266 Commits
1.0.0b1
...
pre-commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4973a55b47 | ||
|
|
1df02de795 | ||
|
|
181aa975f5 | ||
|
|
243fcf7be0 | ||
|
|
8b55d5f5bc | ||
|
|
c9d971b239 | ||
|
|
a047de1e79 | ||
|
|
96550b678a | ||
|
|
1bbbeacc06 | ||
|
|
d906fe8c5d | ||
|
|
79adc7de2a | ||
|
|
27cf17d68c | ||
|
|
ca25444915 | ||
|
|
5415cc471a | ||
|
|
5891f0456b | ||
|
|
36efbc52f8 | ||
|
|
dbcaf22ae0 | ||
|
|
74b863ce22 | ||
|
|
0b1da67264 | ||
|
|
9802473764 | ||
|
|
9d8c66467c | ||
|
|
c9efb93278 | ||
|
|
596984ccbd | ||
|
|
a2e80cc89d | ||
|
|
f49c59898a | ||
|
|
fb1e1063d2 | ||
|
|
939c2b37fd | ||
|
|
da5a5abafa | ||
|
|
784e237831 | ||
|
|
50a74417f6 | ||
|
|
72967f2b17 | ||
|
|
6b2d93aacf | ||
|
|
752f605c53 | ||
|
|
0cd259b6a1 | ||
|
|
684481fd5d | ||
|
|
c639c75637 | ||
|
|
45bec8e9f8 | ||
|
|
519415395f | ||
|
|
c3703985ee | ||
|
|
e6851d946d | ||
|
|
f13aef7568 | ||
|
|
354a8d47cc | ||
|
|
4263e4c85e | ||
|
|
4807b7da89 | ||
|
|
ae503c3096 | ||
|
|
512935abb7 | ||
|
|
a4858da978 | ||
|
|
134ddd9ef6 | ||
|
|
7e929fb78c | ||
|
|
3eeb028f1f | ||
|
|
0384a46720 | ||
|
|
142d22616c | ||
|
|
5ff2c8aa6f | ||
|
|
bba39bdfde | ||
|
|
c1134a5341 | ||
|
|
989f772872 | ||
|
|
d5752b6c76 | ||
|
|
dcdf2dc9b4 | ||
|
|
251099caac | ||
|
|
fa83068cdb | ||
|
|
3ee67e1581 | ||
|
|
a1f1c5e046 | ||
|
|
8e318586c3 | ||
|
|
a099bf647d | ||
|
|
8cb42a9fc1 | ||
|
|
29dda40b33 | ||
|
|
4b066766d9 | ||
|
|
947eb028a9 | ||
|
|
d695d29a6d | ||
|
|
53eceabd11 | ||
|
|
0f7d4f4748 | ||
|
|
0907ca4ad4 | ||
|
|
3397e75dc5 | ||
|
|
cd53b41b49 | ||
|
|
5916407a50 | ||
|
|
e515bcad26 | ||
|
|
5a9cb2b395 | ||
|
|
9ed2a92585 | ||
|
|
e7555ae29c | ||
|
|
73331f28fd | ||
|
|
6531b6b7aa | ||
|
|
c0563ab573 | ||
|
|
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 | ||
|
|
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 |
47
.github/integration-test.py
vendored
47
.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)
|
||||||
|
|
||||||
@@ -218,12 +226,35 @@ def main():
|
|||||||
copy_parser.add_argument("src")
|
copy_parser.add_argument("src")
|
||||||
copy_parser.add_argument("dest")
|
copy_parser.add_argument("dest")
|
||||||
|
|
||||||
run_test_parser = subparsers.add_parser("run-test")
|
run_test_parser = subparsers.add_parser(
|
||||||
run_test_parser.add_argument("--installer-args", action="append")
|
"run-test",
|
||||||
run_test_parser.add_argument("--upgrade-from", default="")
|
help="Runs the bootstrap script in a container, then executes specified integration tests.",
|
||||||
run_test_parser.add_argument("--bootstrap-pip-spec", default="/srv/src")
|
)
|
||||||
run_test_parser.add_argument("container_name")
|
run_test_parser.add_argument(
|
||||||
run_test_parser.add_argument("test_files", nargs="+")
|
"--installer-args",
|
||||||
|
action="append",
|
||||||
|
default=[],
|
||||||
|
help="Additional arguments to pass to bootstrap.py during the main installation. Can be used multiple times.",
|
||||||
|
)
|
||||||
|
run_test_parser.add_argument(
|
||||||
|
"--upgrade-from",
|
||||||
|
default="",
|
||||||
|
help="A version/tag (e.g., 'main', 'v0.1.0') to install first, simulating an upgrade to the current source code.",
|
||||||
|
)
|
||||||
|
run_test_parser.add_argument(
|
||||||
|
"--bootstrap-pip-spec",
|
||||||
|
default="/srv/src",
|
||||||
|
help="The pip specification used by the bootstrap script to install TLJH (for example: '--bootstrap-pip-spec=git+https://github.com/your-username/the-littlest-jupyterhub.git@branch-name'). Defaults to the local source code path.",
|
||||||
|
)
|
||||||
|
run_test_parser.add_argument(
|
||||||
|
"container_name",
|
||||||
|
help="An identifier for the container/test run (for example: 'basic-tests').",
|
||||||
|
)
|
||||||
|
run_test_parser.add_argument(
|
||||||
|
"test_files",
|
||||||
|
nargs="+",
|
||||||
|
help="A list of one or more test files under 'integration-tests/' to be executed.",
|
||||||
|
)
|
||||||
|
|
||||||
show_logs_parser = subparsers.add_parser("show-logs")
|
show_logs_parser = subparsers.add_parser("show-logs")
|
||||||
show_logs_parser.add_argument("container_name")
|
show_logs_parser.add_argument("container_name")
|
||||||
|
|||||||
22
.github/workflows/integration-test.yaml
vendored
22
.github/workflows/integration-test.yaml
vendored
@@ -36,25 +36,28 @@ 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
|
||||||
- name: "Ubuntu 22.04, Py 3.10, from latest"
|
- name: "Ubuntu 22.04, Py 3.10, from latest"
|
||||||
distro_image: "ubuntu:22.04"
|
distro_image: "ubuntu:22.04"
|
||||||
extra_flags: --upgrade-from=latest
|
extra_flags: --upgrade-from=latest
|
||||||
- name: "Ubuntu 22.04, Py 3.10, from 0.2.0"
|
- name: "Ubuntu 22.04, Py 3.10, from 1.*"
|
||||||
distro_image: "ubuntu:22.04"
|
distro_image: "ubuntu:22.04"
|
||||||
extra_flags: --upgrade-from=0.2.0
|
extra_flags: --upgrade-from=1
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
|
|
||||||
@@ -116,10 +119,13 @@ jobs:
|
|||||||
distro_image: "ubuntu:22.04"
|
distro_image: "ubuntu:22.04"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v6
|
||||||
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,
|
||||||
|
|||||||
33
.github/workflows/unit-test.yaml
vendored
33
.github/workflows/unit-test.yaml
vendored
@@ -40,46 +40,33 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: "Ubuntu 20.04, Py 3.9"
|
|
||||||
ubuntu_version: "20.04"
|
|
||||||
python_version: "3.9"
|
|
||||||
- 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@v5
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "${{ matrix.python_version }}"
|
python-version: "${{ matrix.python_version }}"
|
||||||
|
|
||||||
- name: Install venv, git and setup venv
|
- name: Install system dependencies 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 \
|
||||||
|
curl
|
||||||
|
|
||||||
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 +80,4 @@ jobs:
|
|||||||
run: pytest tests
|
run: pytest tests
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
- uses: codecov/codecov-action@v3
|
- uses: codecov/codecov-action@v5
|
||||||
|
|||||||
@@ -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.21.2
|
||||||
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: 7.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
|
|
||||||
# Autoformat: Python code
|
# Autoformat: Python code
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 23.3.0
|
rev: 25.11.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: v6.0.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.3.0"
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
|
|
||||||
|
|||||||
@@ -27,22 +27,15 @@ 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
|
||||||
**Ubuntu 20.04**. Earlier versions of Ubuntu are not supported.
|
**Ubuntu 22.04**. Earlier versions of Ubuntu are not supported.
|
||||||
We have several tutorials to get you started.
|
We have several tutorials to get you started.
|
||||||
|
|
||||||
- Tutorials to create a new server from scratch on a cloud provider & run TLJH
|
- Tutorials to create a new server from scratch on a cloud provider & run TLJH
|
||||||
on it. These are **recommended** if you do not have much experience setting up
|
on it. These are **recommended** if you do not have much experience setting up
|
||||||
servers.
|
servers.
|
||||||
|
|
||||||
- [Digital Ocean](https://the-littlest-jupyterhub.readthedocs.io/en/latest/install/digitalocean.html)
|
- [Digital Ocean](https://the-littlest-jupyterhub.readthedocs.io/en/latest/install/digitalocean.html)
|
||||||
- [OVH](https://the-littlest-jupyterhub.readthedocs.io/en/latest/install/ovh.html)
|
- [OVH](https://the-littlest-jupyterhub.readthedocs.io/en/latest/install/ovh.html)
|
||||||
- [Google Cloud](https://the-littlest-jupyterhub.readthedocs.io/en/latest/install/google.html)
|
- [Google Cloud](https://the-littlest-jupyterhub.readthedocs.io/en/latest/install/google.html)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
38
docs/conf.py
38
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
|
||||||
#
|
#
|
||||||
@@ -111,3 +138,14 @@ rediraffe_redirects = {
|
|||||||
"howto/env/notebook-interfaces": "howto/user-env/notebook-interfaces",
|
"howto/env/notebook-interfaces": "howto/user-env/notebook-interfaces",
|
||||||
"howto/env/server-resources": "howto/user-env/server-resources",
|
"howto/env/server-resources": "howto/user-env/server-resources",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
# Enable Plausible.io stats
|
||||||
|
app.add_js_file(
|
||||||
|
"https://plausible.io/js/pa-B75UO5--FNXYQSG7GBWkf.js", loading_method="async"
|
||||||
|
)
|
||||||
|
app.add_js_file(
|
||||||
|
filename=None,
|
||||||
|
body="window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};plausible.init({hashBasedRouting:true})",
|
||||||
|
)
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ The easiest & safest way to develop & test TLJH is with [Docker](https://www.doc
|
|||||||
--detach \
|
--detach \
|
||||||
--name=tljh-dev \
|
--name=tljh-dev \
|
||||||
--publish 12000:80 \
|
--publish 12000:80 \
|
||||||
|
--env TLJH_BOOTSTRAP_DEV=yes \
|
||||||
|
--env TLJH_BOOTSTRAP_PIP_SPEC=/srv/src \
|
||||||
|
--env PATH=/opt/tljh/hub/bin:${PATH} \
|
||||||
--mount type=bind,source="$(pwd)",target=/srv/src \
|
--mount type=bind,source="$(pwd)",target=/srv/src \
|
||||||
tljh-systemd
|
tljh-systemd
|
||||||
```
|
```
|
||||||
@@ -58,7 +61,6 @@ The easiest & safest way to develop & test TLJH is with [Docker](https://www.doc
|
|||||||
|
|
||||||
8. Make some changes to the repository. You can test easily depending on what
|
8. Make some changes to the repository. You can test easily depending on what
|
||||||
you changed.
|
you changed.
|
||||||
|
|
||||||
- If you changed the `bootstrap/bootstrap.py` script or any of its dependencies,
|
- If you changed the `bootstrap/bootstrap.py` script or any of its dependencies,
|
||||||
you can test it by running `python3 /srv/src/bootstrap/bootstrap.py`.
|
you can test it by running `python3 /srv/src/bootstrap/bootstrap.py`.
|
||||||
- If you changed the `tljh/installer.py` code (or any of its dependencies),
|
- If you changed the `tljh/installer.py` code (or any of its dependencies),
|
||||||
|
|||||||
@@ -28,17 +28,21 @@ against the same installation of TLJH.
|
|||||||
|
|
||||||
### Running integration tests locally
|
### Running integration tests locally
|
||||||
|
|
||||||
You need `docker` installed and callable by the user running
|
You need `docker` or `podman` installed and callable by the user
|
||||||
the integration tests without needing sudo.
|
running the integration tests without needing sudo.
|
||||||
|
|
||||||
You can then run the tests with:
|
First build the container with a Ubuntu-based image:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.github/integration-test.py run-test <name-of-run> <test-file-names>
|
.github/integration-test.py build-image \
|
||||||
|
--build-arg "BASE_IMAGE=ubuntu:22.04"
|
||||||
```
|
```
|
||||||
|
|
||||||
- `<name-of-run>` is an identifier for the tests - you can choose anything you want
|
Then you can then run the tests with the `run-test` function. For usage run:
|
||||||
- `<test-file-names>>` is list of test files (under `integration-tests`) that should be run in one go.
|
|
||||||
|
```bash
|
||||||
|
.github/integration-test.py run-test --help
|
||||||
|
```
|
||||||
|
|
||||||
For example, to run all the basic tests, you would write:
|
For example, to run all the basic tests, you would write:
|
||||||
|
|
||||||
@@ -50,13 +54,17 @@ For example, to run all the basic tests, you would write:
|
|||||||
test_extensions.py
|
test_extensions.py
|
||||||
```
|
```
|
||||||
|
|
||||||
This will run the tests in the three files against the same installation
|
This will run the tests in the four files against the same installation
|
||||||
of TLJH and report errors.
|
of TLJH and report errors.
|
||||||
|
|
||||||
If you would like to run the tests with a custom pip spec for the bootstrap script, you can use the `--bootstrap-pip-spec`
|
If you would like to run the tests with a custom `pip` spec for the bootstrap script, you can use the `--bootstrap-pip-spec`
|
||||||
parameter:
|
parameter:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.github/integration-test.py run-test <name-of-run> <test-file-names> \
|
.github/integration-test.py run-test custom-pip-spec \
|
||||||
|
test_hub.py \
|
||||||
|
test_proxy.py \
|
||||||
|
test_install.py \
|
||||||
|
test_extensions.py \
|
||||||
--bootstrap-pip-spec="git+https://github.com/your-username/the-littlest-jupyterhub.git@branch-name"
|
--bootstrap-pip-spec="git+https://github.com/your-username/the-littlest-jupyterhub.git@branch-name"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ renew them for you before they expire.
|
|||||||
|
|
||||||
## Manual HTTPS with existing key and certificate
|
## Manual HTTPS with existing key and certificate
|
||||||
|
|
||||||
You may already have an SSL key and certificate.
|
You may already have an SSL key and certificate for your domain.
|
||||||
If so, you can tell your deployment to use these files:
|
If so, you can tell your deployment to use these files:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -111,7 +111,7 @@ Finally, you can reload the proxy to load the new configuration:
|
|||||||
sudo tljh-config reload proxy
|
sudo tljh-config reload proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
and now access your Hub securely at <https://yourhub.yourdomain.edu>.
|
and now access your Hub securely at the domain associated with your certificate.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -38,6 +38,8 @@ these namespacing settings might be a bit too strict and prevent users from acce
|
|||||||
|
|
||||||
To override the `jupyterhub` settings, it is possible to provide a custom `/etc/systemd/system/jupyterhub.service.d/override.conf` file.
|
To override the `jupyterhub` settings, it is possible to provide a custom `/etc/systemd/system/jupyterhub.service.d/override.conf` file.
|
||||||
|
|
||||||
|
You can create this file with `sudo systemctl edit jupyterhub.service`
|
||||||
|
|
||||||
Here is an example for the content of the file:
|
Here is an example for the content of the file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -72,7 +74,7 @@ The output should look like the following:
|
|||||||
```
|
```
|
||||||
|
|
||||||
To override the `traefik` settings, create a new file under `/etc/systemd/system/traefik.service.d/override.conf`
|
To override the `traefik` settings, create a new file under `/etc/systemd/system/traefik.service.d/override.conf`
|
||||||
and follow the same steps.
|
(or use `sudo systemctl edit traefik.service`) and follow the same steps.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
@@ -24,7 +24,6 @@ application to your `tljh` configuration.
|
|||||||
1. Create a user pool [Getting Started with User Pool](https://docs.aws.amazon.com/cognito/latest/developerguide/getting-started-with-cognito-user-pools.html).
|
1. Create a user pool [Getting Started with User Pool](https://docs.aws.amazon.com/cognito/latest/developerguide/getting-started-with-cognito-user-pools.html).
|
||||||
|
|
||||||
When you have completed creating a user pool, app, and domain you should have the following settings available to you:
|
When you have completed creating a user pool, app, and domain you should have the following settings available to you:
|
||||||
|
|
||||||
- **App client id**: From the App client page
|
- **App client id**: From the App client page
|
||||||
|
|
||||||
- **App client secret** From the App client page
|
- **App client secret** From the App client page
|
||||||
@@ -149,9 +148,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"]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ You'll need a GitHub account in order to complete these steps.
|
|||||||
## Step 1: Create a GitHub application
|
## Step 1: Create a GitHub application
|
||||||
|
|
||||||
1. Go to the [GitHub OAuth app creation page](https://github.com/settings/applications/new).
|
1. Go to the [GitHub OAuth app creation page](https://github.com/settings/applications/new).
|
||||||
|
|
||||||
- **Application name**: Choose a descriptive application name (e.g. `tljh`)
|
- **Application name**: Choose a descriptive application name (e.g. `tljh`)
|
||||||
|
|
||||||
- **Homepage URL**: Use the IP address or URL of your JupyterHub. e.g. `` http(s)://<my-tljh-url>` ``.
|
- **Homepage URL**: Use the IP address or URL of your JupyterHub. e.g. `` http(s)://<my-tljh-url>` ``.
|
||||||
@@ -91,10 +90,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
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ and create a new project:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- You will have to fill a form with:
|
- You will have to fill a form with:
|
||||||
|
|
||||||
- **Application type**: Choose _Web application_
|
- **Application type**: Choose _Web application_
|
||||||
|
|
||||||
- **Name**: A descriptive name for your OAuth client ID (e.g. `tljh-client`)
|
- **Name**: A descriptive name for your OAuth client ID (e.g. `tljh-client`)
|
||||||
|
|||||||
@@ -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,48 @@
|
|||||||
(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`](https://github.com/jupyterlab/jupyterlab/)
|
||||||
2. [nteract](https://nteract.io/)
|
and [`classic`](https://github.com/jupyter/notebook), which
|
||||||
|
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.
|
||||||
@@ -180,7 +180,6 @@ To upgrade the Python version of the user environment, one can:
|
|||||||
environment, but you may do so manually.
|
environment, but you may do so manually.
|
||||||
|
|
||||||
**Steps:**
|
**Steps:**
|
||||||
|
|
||||||
1. Activate the user environment, if using ssh. If the terminal was
|
1. Activate the user environment, if using ssh. If the terminal was
|
||||||
started with JupyterHub, this step can be skipped:
|
started with JupyterHub, this step can be skipped:
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ We start by creating the Virtual Machine in which we can run TLJH (The Littlest
|
|||||||
```
|
```
|
||||||
|
|
||||||
5. **Choose an Ubuntu server for your VM**:
|
5. **Choose an Ubuntu server for your VM**:
|
||||||
|
|
||||||
- Click `Ubuntu Server 22.04 LTS.`
|
- Click `Ubuntu Server 22.04 LTS.`
|
||||||
|
|
||||||
- Make sure `Resource Manager` is selected in the next screen and click **Create**
|
- Make sure `Resource Manager` is selected in the next screen and click **Create**
|
||||||
@@ -61,7 +60,6 @@ We start by creating the Virtual Machine in which we can run TLJH (The Littlest
|
|||||||
```
|
```
|
||||||
|
|
||||||
6. Customise the Virtual Machine basics:
|
6. Customise the Virtual Machine basics:
|
||||||
|
|
||||||
- **Subscription**. Choose the "Free Trial" if this is what you're using. Otherwise, choose a different plan. This is the billing account that will be charged.
|
- **Subscription**. Choose the "Free Trial" if this is what you're using. Otherwise, choose a different plan. This is the billing account that will be charged.
|
||||||
- **Resource group**. Resource groups let you keep your Azure tools/resources together in an availability region (e.g. WestEurope). If you already have one you'd like to use it select that resource.
|
- **Resource group**. Resource groups let you keep your Azure tools/resources together in an availability region (e.g. WestEurope). If you already have one you'd like to use it select that resource.
|
||||||
|
|
||||||
@@ -89,7 +87,6 @@ We start by creating the Virtual Machine in which we can run TLJH (The Littlest
|
|||||||
- **Inbound port rules**. Leave the defaults for now, and we will update these later on in the Network configuration step.
|
- **Inbound port rules**. Leave the defaults for now, and we will update these later on in the Network configuration step.
|
||||||
|
|
||||||
7. Before clicking on "Next" we need to select the RAM size for the image.
|
7. Before clicking on "Next" we need to select the RAM size for the image.
|
||||||
|
|
||||||
- For this we need to make sure we have enough RAM to accommodate your users. For example, if each user needs 2GB of RAM, and you have 10 total users, you need at least 20GB of RAM on the machine. It's also good to have a few GB of "buffer" RAM beyond what you think you'll need.
|
- For this we need to make sure we have enough RAM to accommodate your users. For example, if each user needs 2GB of RAM, and you have 10 total users, you need at least 20GB of RAM on the machine. It's also good to have a few GB of "buffer" RAM beyond what you think you'll need.
|
||||||
|
|
||||||
- Click on **Change size** (see image below)
|
- Click on **Change size** (see image below)
|
||||||
@@ -105,7 +102,6 @@ We start by creating the Virtual Machine in which we can run TLJH (The Littlest
|
|||||||
- Select a suitable image (to check available images and prices in your region [click on this link](https://azuremarketplace.microsoft.com/en-gb/marketplace/apps/Canonical.UbuntuServer?tab=PlansAndPrice/?wt.mc_id=TLJH-github-taallard)).
|
- Select a suitable image (to check available images and prices in your region [click on this link](https://azuremarketplace.microsoft.com/en-gb/marketplace/apps/Canonical.UbuntuServer?tab=PlansAndPrice/?wt.mc_id=TLJH-github-taallard)).
|
||||||
|
|
||||||
8. Disks (Storage):
|
8. Disks (Storage):
|
||||||
|
|
||||||
- **Disk options**: select the OS disk type there are options for SDD and HDD. **SSD persistent disk** gives you a faster but more expensive disk than HDD.
|
- **Disk options**: select the OS disk type there are options for SDD and HDD. **SSD persistent disk** gives you a faster but more expensive disk than HDD.
|
||||||
|
|
||||||
- **Data disk**. Click on create and attach a new disk. Select an appropriate type and size and click ok.
|
- **Data disk**. Click on create and attach a new disk. Select an appropriate type and size and click ok.
|
||||||
@@ -120,7 +116,6 @@ We start by creating the Virtual Machine in which we can run TLJH (The Littlest
|
|||||||
```
|
```
|
||||||
|
|
||||||
9. Networking
|
9. Networking
|
||||||
|
|
||||||
- **Virtual network**. Leave the default values selected.
|
- **Virtual network**. Leave the default values selected.
|
||||||
- **Subnet**. Leave the default values selected.
|
- **Subnet**. Leave the default values selected.
|
||||||
- **Public IP address**.Leave the default values selected. This will make your server accessible from a browser.
|
- **Public IP address**.Leave the default values selected. This will make your server accessible from a browser.
|
||||||
@@ -132,9 +127,7 @@ We start by creating the Virtual Machine in which we can run TLJH (The Littlest
|
|||||||
```
|
```
|
||||||
|
|
||||||
10. Management
|
10. Management
|
||||||
|
|
||||||
- Monitoring
|
- Monitoring
|
||||||
|
|
||||||
- **Boot diagnostics**. Choose "On".
|
- **Boot diagnostics**. Choose "On".
|
||||||
- **OS guest diagnostics**. Choose "Off".
|
- **OS guest diagnostics**. Choose "Off".
|
||||||
- **Diagnostics storage account**. Leave as the default.
|
- **Diagnostics storage account**. Leave as the default.
|
||||||
@@ -150,7 +143,6 @@ We start by creating the Virtual Machine in which we can run TLJH (The Littlest
|
|||||||
```
|
```
|
||||||
|
|
||||||
11. Advanced settings
|
11. Advanced settings
|
||||||
|
|
||||||
- **Extensions**. Make sure there are no extensions listed
|
- **Extensions**. Make sure there are no extensions listed
|
||||||
- **Cloud init**. We are going to use this section to install TLJH directly into our Virtual Machine.
|
- **Cloud init**. We are going to use this section to install TLJH directly into our Virtual Machine.
|
||||||
|
|
||||||
@@ -177,7 +169,6 @@ We start by creating the Virtual Machine in which we can run TLJH (The Littlest
|
|||||||
12. Check the summary and confirm the creation of your Virtual Machine.
|
12. Check the summary and confirm the creation of your Virtual Machine.
|
||||||
|
|
||||||
13. Check that the creation of your Virtual Machine worked.
|
13. Check that the creation of your Virtual Machine worked.
|
||||||
|
|
||||||
- Wait for the virtual machine to be created. This might take about 5-10 minutes.
|
- Wait for the virtual machine to be created. This might take about 5-10 minutes.
|
||||||
|
|
||||||
- After completion, you should see a similar screen to the one below:
|
- After completion, you should see a similar screen to the one below:
|
||||||
|
|||||||
@@ -1,152 +1,112 @@
|
|||||||
(install-jetstream)=
|
(install-jetstream)=
|
||||||
|
|
||||||
# Installing on Jetstream
|
# Installing on Jetstream2
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
By the end of this tutorial, you should have a JupyterHub with some admin
|
By the end of this tutorial, you should have a JupyterHub with some admin
|
||||||
users and a user environment with packages you want installed running on
|
users and a user environment with packages you want installed running on
|
||||||
[Jetstream](https://jetstream-cloud.org/).
|
[Jetstream2](https://jetstream-cloud.org/).
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
1. A Jetstream account with an XSEDE allocation; for more information,
|
1. An ACCESS ID, and membership in an allocation with access to Jetstream2 resources; for more information,
|
||||||
see the [Jetstream Allocations help page](http://wiki.jetstream-cloud.org/Jetstream+Allocations).
|
see the [Jetstream2 Allocations Overview page](https://docs.jetstream-cloud.org/alloc/overview/) and the [Get started with Jetstream2 guide](https://jetstream-cloud.org/get-started/).
|
||||||
|
|
||||||
## Step 1: Installing The Littlest JupyterHub
|
## Step 1: Launch a Jetstream2 instance
|
||||||
|
|
||||||
Let's create the server on which we can run JupyterHub.
|
We'll create a new Jetstream2 instance:
|
||||||
|
|
||||||
1. Log in to [the Jetstream portal](https://use.jetstream-cloud.org/). You need an allocation
|
1. Log in to the [Jetstream2 portal](https://use.jetstream-cloud.org/). You must have (and select) an allocation in order to launch instances. Click the allocation you want to charge.
|
||||||
to launch instances.
|
2. Click **Create** ➜ **Instance**.
|
||||||
|
3. From the list of images, select **Ubuntu 24.04** (Jammy or newer is required for current TLJH releases).
|
||||||
|
4. In the **Create Instance** dialog:
|
||||||
|
1. Set a descriptive **Instance Name** (this is used in the default hostname and helps users recognize it).
|
||||||
|
2. Choose an **Instance Size**. We suggest `m3.small` (2 vCPUs / 6 GiB RAM) or larger for more than a couple of users. The absolute minimum TLJH can start with is about **1 GiB** RAM, but you'll quickly run out with real workloads.
|
||||||
|
- See the resource estimation guide: [Choosing resources](/howto/admin/resource-estimation) for help picking CPU, RAM, and disk.
|
||||||
|
3. (Optional) Increase the **Volume Size** if you expect many users or large datasets. You can not easily shrink later.
|
||||||
|
5. Launch the instance (click **Create** button at the bottom of the form).
|
||||||
|
|
||||||
2. Select the **Launch New Instance** option to get going.
|
## Step 2: Install The Littlest JupyterHub
|
||||||
|
|
||||||
```{image} ../images/providers/jetstream/launch-instance-first-button.png
|
1. Wait a few minutes for the instance to show the status "Ready"
|
||||||
:alt: Launch new instance button with description.
|
2. Copy the **Hostname** under **Credentials**, it will be of the form: `yourinstancename.xxx0000000.projects.jetstream-cloud.org`, where `xxx000000` is the allocation ID. Keep it handy, we will use it multiple times in the next steps.
|
||||||
```
|
|
||||||
|
|
||||||
This takes you to a page with a list of base images you can choose for your
|
3. SSH into the instance with the `exouser` user:
|
||||||
server.
|
|
||||||
|
|
||||||
3. Under **Image Search**, search for **Ubuntu 22.04**, and select the
|
|
||||||
**Ubuntu 22.04 Devel and Docker** image.
|
|
||||||
|
|
||||||
```{image} ../images/providers/jetstream/select-image.png
|
|
||||||
:alt: Select Ubuntu 22.04 x64 image from image list
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Once selected, you will see more information about this image. Click the
|
|
||||||
**Launch** button on the top right.
|
|
||||||
|
|
||||||
```{image} ../images/providers/jetstream/launch-instance-second-button.png
|
|
||||||
:alt: Launch selected image with Launch button on top right
|
|
||||||
```
|
|
||||||
|
|
||||||
5. A dialog titled **Launch an Instance / Basic Options** pops up, with various
|
|
||||||
options for configuring your instance.
|
|
||||||
|
|
||||||
```{image} ../images/providers/jetstream/launch-instance-dialog.png
|
|
||||||
:alt: Launch an Instance / Basic Options dialog box
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Give your server a descriptive **Instance Name**.
|
|
||||||
|
|
||||||
2. Select an appropriate **Instance Size**. We suggest m1.medium or larger.
|
|
||||||
Make sure your instance has at least **1GB** of RAM.
|
|
||||||
|
|
||||||
Check out our guide on How To [](/howto/admin/resource-estimation) to help pick
|
|
||||||
how much Memory, CPU & disk space your server needs.
|
|
||||||
|
|
||||||
3. If you have multiple allocations, make sure you are 'charging' this server
|
|
||||||
to the correct allocation.
|
|
||||||
|
|
||||||
6. Click the **Advanced Options** link in the bottom left of the popup. This
|
|
||||||
lets us configure what the server should do when it starts up. We will use
|
|
||||||
this to install The Littlest JupyterHub.
|
|
||||||
|
|
||||||
A dialog titled **Launch an Instance / Advanced Options** should pop up.
|
|
||||||
|
|
||||||
```{image} ../images/providers/jetstream/add-deployment-script-dialog.png
|
|
||||||
:alt: Dialog box allowing you to add a new script.
|
|
||||||
```
|
|
||||||
|
|
||||||
7. Click the **Create New Script** button. This will open up another dialog
|
|
||||||
box!
|
|
||||||
|
|
||||||
```{image} ../images/providers/jetstream/create-script-dialog.png
|
|
||||||
:alt: Launch an Instance / Advanced Options dialog box
|
|
||||||
```
|
|
||||||
|
|
||||||
8. Under **Input Type**, select **Raw Text**. This should make a text box titled
|
|
||||||
**Raw Text** visible on the right side of the dialog box.
|
|
||||||
Copy the text below, and paste it into the **Raw Text** text box. Replace
|
|
||||||
`<admin-user-name>` with the name of the first **admin user** for this
|
|
||||||
JupyterHub. This admin user can log in after the JupyterHub is set up, and
|
|
||||||
can configure it to their needs. **Remember to add your username**!
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
ssh exouser@yourinstancename.xxx0000000.projects.jetstream-cloud.org
|
||||||
curl -L https://tljh.jupyter.org/bootstrap.py \
|
|
||||||
| sudo python3 - \
|
|
||||||
--admin <admin-user-name>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::{note}
|
Need the passphrase? In Exosphere, open **Instances**, select your
|
||||||
See [](/topic/installer-actions) if you want to understand exactly what the installer is doing.
|
JupyterHub instance, and expand **Credentials**. Click **Show Passphrase**
|
||||||
[](/topic/customizing-installer) documents other options that can be passed to the installer.
|
next to the `exouser` entry to reveal or copy it before running the
|
||||||
:::
|
`ssh` command.
|
||||||
|
|
||||||
9. Under **Execution Strategy Type**, select **Run script on first boot**.
|
4. Run the TLJH bootstrap script, replace <admin-user-name> with the name of the first admin user for this JupyterHub. Choose any name you like (don’t forget to remove the brackets!). This admin user can log in after the JupyterHub is set up, and can configure it to their needs.
|
||||||
|
|
||||||
10. Under **Deployment Type**, select **Wait for script to complete**.
|
```bash
|
||||||
|
curl -L https://tljh.jupyter.org/bootstrap.py | sudo -E python3 - --admin <admin-user-name>
|
||||||
11. Click the **Save and Add Script** button on the bottom right. This should hide
|
|
||||||
the dialog box.
|
|
||||||
|
|
||||||
12. Click the **Continue to Launch** button on the bottom right. This should put you
|
|
||||||
back in the **Launch an Instance / Basic Options** dialog box again.
|
|
||||||
|
|
||||||
13. Click the **Launch Instance** button on the bottom right. This should turn it
|
|
||||||
into a spinner, and your server is getting created!
|
|
||||||
|
|
||||||
```{image} ../images/providers/jetstream/launching-spinner.png
|
|
||||||
:alt: Launch button turns into a spinner
|
|
||||||
```
|
```
|
||||||
|
|
||||||
14. You'll now be shown a dashboard with all your servers and their states. The
|
5. Open the Hostname in a web browser (http on port 80). You should see the JupyterHub login page. Your browser will warn about the site not being secure (no HTTPS)—we'll enable HTTPS in the next step. Do not login yet, first setup HTTPS, so we avoid transmitting the password in clear text.
|
||||||
server you just launched will progress through various stages of set up,
|
|
||||||
and you can see the progress here.
|
|
||||||
|
|
||||||
```{image} ../images/providers/jetstream/deployment-in-progress.png
|
## Step 2: Enable HTTPS
|
||||||
:alt: Instances dashboard showing deployment in progress.
|
|
||||||
|
Encrypted (HTTPS) access is strongly recommended before inviting users.
|
||||||
|
|
||||||
|
See the full guide: [Enable HTTPS](/howto/admin/https). Below is a quick recipe for using the default Jetstream-provided hostname.
|
||||||
|
|
||||||
|
1. In the terminal inside the instance, configure Let's Encrypt (replace with a real email you control, and the correct hostname):
|
||||||
|
```bash
|
||||||
|
sudo tljh-config set https.enabled true
|
||||||
|
sudo tljh-config set https.letsencrypt.email you@example.com
|
||||||
|
sudo tljh-config add-item https.letsencrypt.domains yourinstancename.xxx0000000.projects.jetstream-cloud.org
|
||||||
|
sudo tljh-config reload proxy
|
||||||
|
```
|
||||||
|
2. Wait ~30–60 seconds, then reload the site using https://. If certificate issuance fails, check the logs:
|
||||||
|
```bash
|
||||||
|
sudo journalctl -u traefik --since "10 minutes ago" | grep -i acme
|
||||||
```
|
```
|
||||||
|
|
||||||
15. It will take about ten minutes for your server to come up. The status will
|
Tips:
|
||||||
say **Active** and the progress bar will be a solid green. At this point,
|
|
||||||
your JupyterHub is ready for use!
|
|
||||||
|
|
||||||
16. Copy the **IP Address** of your server, and try accessing it from a web
|
- Make sure ports 80 and 443 are open in your Jetstream security group (they are open by default for new projects; adjust only if you customized network policies).
|
||||||
browser. It should give you a JupyterHub login page.
|
- If you later attach a custom domain, add it with another `add-item` command and reload the proxy again.
|
||||||
|
|
||||||
```{image} ../images/first-login.png
|
## Step 3: Log in as the administrative user and set a password
|
||||||
:alt: JupyterHub log-in page
|
|
||||||
```
|
|
||||||
|
|
||||||
17. Login using the **admin user name** you used in step 8, and a password. Use a
|
1. Now log in with the `<admin-user-name>` at https://yourinstancename.xxx000000.projects.jetstream-cloud.org. Since this is the first login, you'll be prompted to set a password. Choose a strong password and store it safely. This password is now the credential for that admin user.
|
||||||
strong password & note it down somewhere, since this will be the password for
|
2. Congratulations, you have a running working JupyterHub!
|
||||||
the admin user account from now on.
|
|
||||||
|
|
||||||
18. Congratulations, you have a running working JupyterHub!
|
## Step 4: Adding more users
|
||||||
|
|
||||||
## Step 2: Adding more users
|
|
||||||
|
|
||||||
```{include} add-users.md
|
```{include} add-users.md
|
||||||
|
|
||||||
```
|
|
||||||
|
Next common tasks:
|
||||||
## Step 3: Install conda / pip packages for all users
|
|
||||||
|
- [](howto-admin-admin-users)
|
||||||
```{include} add-packages.md
|
- [](howto-user-env-user-environment-apt)
|
||||||
|
- [](howto-admin-enable-extensions)
|
||||||
|
- []topic-installer-upgrade-actions)
|
||||||
|
|
||||||
|
Browse the full [How-To index](/howto/index) for more.
|
||||||
|
|
||||||
|
## Ask for help
|
||||||
|
|
||||||
|
Need a hand?
|
||||||
|
|
||||||
|
- For Jetstream2 specific questions (allocations, quotas, instance lifecycle, networking, etc.), use the [Jetstream support resources](https://docs.jetstream-cloud.org/overview/support/).
|
||||||
|
- For The Littlest JupyterHub usage, configuration, or upgrade questions, search or post in the [Jupyter forum TLJH category](https://discourse.jupyter.org/c/jupyterhub/tljh).
|
||||||
|
- If you believe you have found a TLJH bug or have a clear documentation improvement, open an issue (or pull request if you have a proposed fix) in the [TLJH GitHub repository](https://github.com/jupyterhub/the-littlest-jupyterhub).
|
||||||
|
|
||||||
|
When asking for help about TLJH, it is often useful to provide:
|
||||||
|
|
||||||
|
- A short description of what you were trying to do and what happened instead
|
||||||
|
- Relevant log excerpts (see [](/troubleshooting/logs))
|
||||||
|
- Your TLJH version (`sudo tljh-config show | grep version` if present in config) and the output of `lsb_release -a` for the OS
|
||||||
|
- Any custom installer flags or `tljh-config` changes you have applied
|
||||||
|
|
||||||
|
This information helps others debug and answer more quickly.
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,24 +1,139 @@
|
|||||||
|
(changelog)=
|
||||||
|
|
||||||
# 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.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 +142,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 +161,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,15 +182,15 @@ 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 |
|
||||||
@@ -78,13 +198,13 @@ The changes in the respective environments between TLJH version 0.2.0 and
|
|||||||
| [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 +221,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 +251,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 +272,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
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ the idle culler configuration can be extended beyond tljh-config options, using
|
|||||||
|
|
||||||
## Default settings
|
## Default settings
|
||||||
|
|
||||||
By default, JupyterHub will ping the user notebook servers every 60s to check their
|
By default, JupyterHub will ping the user notebook servers every 10 min to check their
|
||||||
status. Every server found to be idle for more than 10 minutes will be culled.
|
status. Every server found to be idle for more than 1 hour will be culled.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
services.cull.every = 60
|
services.cull.every = 600
|
||||||
services.cull.timeout = 600
|
services.cull.timeout = 3600
|
||||||
```
|
```
|
||||||
|
|
||||||
Because the servers don't have a maximum age set, an active server will not be shut down
|
Because the servers don't have a maximum age set, an active server will not be shut down
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -227,7 +227,7 @@ it after an argument like `remove-item` gives information about this specific co
|
|||||||
```bash
|
```bash
|
||||||
sudo tljh-config --help
|
sudo tljh-config --help
|
||||||
|
|
||||||
usage: tljh-config [-h] [--config-path CONFIG_PATH] {show,unset,set,add-item,remove-item,reload} ...
|
usage: tljh-config [-h] [--config-path CONFIG_PATH] [--validate] [--no-validate] {show,unset,set,add-item,remove-item,reload} ...
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
{show,unset,set,add-item,remove-item,reload}
|
{show,unset,set,add-item,remove-item,reload}
|
||||||
@@ -238,10 +238,12 @@ positional arguments:
|
|||||||
remove-item Remove a value from a list for a configuration property
|
remove-item Remove a value from a list for a configuration property
|
||||||
reload Reload a component to apply configuration change
|
reload Reload a component to apply configuration change
|
||||||
|
|
||||||
optional arguments:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--config-path CONFIG_PATH
|
--config-path CONFIG_PATH
|
||||||
Path to TLJH config.yaml file
|
Path to TLJH config.yaml file
|
||||||
|
--validate Validate the TLJH config
|
||||||
|
--no-validate Do not validate the TLJH config
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -22,11 +22,9 @@ container technology in administering user sessions.
|
|||||||
The choice between TLJH and Z2JH ultimately comes down to only a few questions:
|
The choice between TLJH and Z2JH ultimately comes down to only a few questions:
|
||||||
|
|
||||||
1. Do you want your hub and all users to live on a **single, larger machine** vs. spreading users on a **cluster of smaller machines** that are scaled up or down?
|
1. Do you want your hub and all users to live on a **single, larger machine** vs. spreading users on a **cluster of smaller machines** that are scaled up or down?
|
||||||
|
|
||||||
- If you can use a single machine, we recommend **The Littlest JupyterHub**.
|
- If you can use a single machine, we recommend **The Littlest JupyterHub**.
|
||||||
- If you wish to use multiple machines, we recommend **Zero to JupyterHub for Kubernetes**.
|
- If you wish to use multiple machines, we recommend **Zero to JupyterHub for Kubernetes**.
|
||||||
|
|
||||||
2. Do you **need to use container technology**?
|
2. Do you **need to use container technology**?
|
||||||
|
|
||||||
- If no, we recommend **The Littlest JupyterHub**.
|
- If no, we recommend **The Littlest JupyterHub**.
|
||||||
- If yes, we recommend **Zero to JupyterHub for Kubernetes**.
|
- If yes, we recommend **Zero to JupyterHub for Kubernetes**.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ easier. Here are some very basic tips on effective `journalctl` usage.
|
|||||||
[less](<https://en.wikipedia.org/wiki/Less_(Unix)>). This allows you to
|
[less](<https://en.wikipedia.org/wiki/Less_(Unix)>). This allows you to
|
||||||
scroll up / down, search for specific words, etc. Some common keyboard shortcuts
|
scroll up / down, search for specific words, etc. Some common keyboard shortcuts
|
||||||
are:
|
are:
|
||||||
|
|
||||||
- Arrow keys to move up / down / left / right
|
- Arrow keys to move up / down / left / right
|
||||||
- `G` to navigate to the end of the logs
|
- `G` to navigate to the end of the logs
|
||||||
- `g` to navigate to the start of the logs
|
- `g` to navigate to the start of the logs
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Systemd inside a Docker container, for CI only
|
# Systemd inside a Docker container, for CI and local development
|
||||||
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:
|
||||||
@@ -28,9 +28,4 @@ RUN systemctl set-default multi-user.target
|
|||||||
|
|
||||||
STOPSIGNAL SIGRTMIN+3
|
STOPSIGNAL SIGRTMIN+3
|
||||||
|
|
||||||
# Uncomment these lines for a development install
|
|
||||||
#ENV TLJH_BOOTSTRAP_DEV=yes
|
|
||||||
#ENV TLJH_BOOTSTRAP_PIP_SPEC=/srv/src
|
|
||||||
#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,41 +8,42 @@ 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,
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
output = proc.stdout.decode()
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
"jupyterlab 3.",
|
"jupyterlab",
|
||||||
"nbgitpuller 1.",
|
"nbgitpuller",
|
||||||
"jupyter_resource_usage",
|
"jupyter_resource_usage",
|
||||||
]
|
]
|
||||||
|
|
||||||
for e in extensions:
|
for e in extensions:
|
||||||
assert e in proc.stderr.decode()
|
assert e in output, f"'{e}' not found in server extensions: {output}"
|
||||||
|
|
||||||
|
|
||||||
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.1.dev"
|
||||||
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.1.dev",
|
||||||
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"))
|
||||||
|
|
||||||
|
|
||||||
@@ -217,7 +220,8 @@ def test_cli_remove_int(tljh_dir):
|
|||||||
("x", "x"),
|
("x", "x"),
|
||||||
("1x", "1x"),
|
("1x", "1x"),
|
||||||
("1.2x", "1.2x"),
|
("1.2x", "1.2x"),
|
||||||
(None, None),
|
("None", None),
|
||||||
|
("none", None),
|
||||||
("", ""),
|
("", ""),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
13
tests/test_config_schema.py
Normal file
13
tests/test_config_schema.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"""
|
||||||
|
Unit test functions to test JSON Schema validation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
|
||||||
|
from tljh.config_schema import config_schema
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_config_json_schema():
|
||||||
|
"""Validate that the JSON schema fits its $schema specification"""
|
||||||
|
validator_class = jsonschema.validators.validator_for(config_schema)
|
||||||
|
validator_class.check_schema(config_schema)
|
||||||
@@ -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
|
||||||
@@ -160,12 +161,6 @@ def _specifier(version):
|
|||||||
"mamba": ">=1.1.0",
|
"mamba": ">=1.1.0",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# too-old Python (3.7), abort
|
|
||||||
(
|
|
||||||
"miniconda",
|
|
||||||
"4.7.10",
|
|
||||||
ValueError,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_ensure_user_environment(
|
def test_ensure_user_environment(
|
||||||
|
|||||||
@@ -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,
|
||||||
]
|
]
|
||||||
|
|||||||
142
tljh/config.py
142
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"]
|
||||||
@@ -289,8 +317,8 @@ def reload_component(component):
|
|||||||
|
|
||||||
def parse_value(value_str):
|
def parse_value(value_str):
|
||||||
"""Parse a value string"""
|
"""Parse a value string"""
|
||||||
if value_str is None:
|
if value_str.lower() == "none":
|
||||||
return value_str
|
return None
|
||||||
if re.match(r"^\d+$", value_str):
|
if re.match(r"^\d+$", value_str):
|
||||||
return int(value_str)
|
return int(value_str)
|
||||||
elif re.match(r"^\d+\.\d*$", value_str):
|
elif re.match(r"^\d+\.\d*$", value_str):
|
||||||
@@ -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:
|
||||||
|
|||||||
130
tljh/config_schema.py
Normal file
130
tljh/config_schema.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
"""
|
||||||
|
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": {
|
||||||
|
"anyOf": [
|
||||||
|
{"type": "string"},
|
||||||
|
{"type": "null"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cpu": {
|
||||||
|
"anyOf": [
|
||||||
|
{"type": "number", "minimum": 0},
|
||||||
|
{"type": "null"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"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"},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ Config should never append or mutate, only set. Functions here could
|
|||||||
be called many times per lifetime of a jupyterhub.
|
be called many times per lifetime of a jupyterhub.
|
||||||
|
|
||||||
Traitlets that modify the startup of JupyterHub should not be here.
|
Traitlets that modify the startup of JupyterHub should not be here.
|
||||||
FIXME: A strong feeling that JSON Schema should be involved somehow.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -52,7 +51,7 @@ default = {
|
|||||||
"password": "",
|
"password": "",
|
||||||
},
|
},
|
||||||
"user_environment": {
|
"user_environment": {
|
||||||
"default_app": "classic",
|
"default_app": "jupyterlab",
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"cull": {
|
"cull": {
|
||||||
@@ -199,6 +198,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 +238,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
|
||||||
@@ -295,12 +293,16 @@ def ensure_user_environment(user_requirements_txt_file):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if user_requirements_txt_file:
|
if user_requirements_txt_file:
|
||||||
# FIXME: This currently fails hard, should fail soft and not abort installer
|
try:
|
||||||
conda.ensure_pip_requirements(
|
conda.ensure_pip_requirements(
|
||||||
USER_ENV_PREFIX,
|
USER_ENV_PREFIX,
|
||||||
user_requirements_txt_file,
|
user_requirements_txt_file,
|
||||||
upgrade=True,
|
upgrade=True,
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to install requirements for user env from {user_requirements_txt_file}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def ensure_admins(admin_password_list):
|
def ensure_admins(admin_password_list):
|
||||||
@@ -450,13 +452,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())))
|
||||||
@@ -527,11 +534,12 @@ def main():
|
|||||||
ensure_admins(args.admin)
|
ensure_admins(args.admin)
|
||||||
ensure_usergroups()
|
ensure_usergroups()
|
||||||
if args.user_requirements_txt_url:
|
if args.user_requirements_txt_url:
|
||||||
logger.info("installing packages from user_requirements_txt_url")
|
logger.info("Installing packages from user_requirements_txt_url")
|
||||||
ensure_user_environment(args.user_requirements_txt_url)
|
ensure_user_environment(args.user_requirements_txt_url)
|
||||||
|
|
||||||
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.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.2,<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,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.7,<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