Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jeflem committed Sep 8, 2023
1 parent 56db84c commit f38d7fd
Show file tree
Hide file tree
Showing 67 changed files with 4,420 additions and 1 deletion.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/.idea
*.iml
*.log
*.xml
*.image
*.tgz
20_users.py
30_lms.py
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
# Ananke Jupyter Distribution

Ananke will be published here in September 2023.
The Ananke project provides preconfigured [JupyterHub](https://jupyter.org/hub) images for [Podman](https://podman.io) (a [Docker](https://www.docker.com)-like containerization tool) with a focus on integrating [JupyterLab](https://jupyter.org) and [nbgrader](https://nbgrader.readthedocs.io) into learning management systems (LMS) like [Moodle](https://moodle.org), [Canvas](https://www.instructure.com/canvas) and many other.

The project's core is the Kore service providing GUI based course management and an [LTI 1.3](https://en.wikipedia.org/wiki/Learning_Tools_Interoperability) interface for nbgrader.

Target group are administrators of small to medium sized JupyterHubs used in teaching environments with a handfull of courses. The project's focus is not on large-scale JupyterHubs with thousands of users but on:
* easy setup and operation for instructors and students,
* advice and preconfiguration for administrators,
* flexibility to implement different application scenarios.

Also have a look at [Ananke website](https://gauss.fh-zwickau.de/ananke).

## Overall architecture

The structure of an Ananke based JupyterHub deployment is as follows:
* A host machine runs one or several Podman containers.
* Each container represents one JupyterHub, which may be used by a whole university or a department or by only one intructor depending on individual configuration needs. One host machine may provide several JupyterHubs with different configurations and features.
* Each JupyterHub provides individual JupyterLabs for all its users (instructors and students).

## Available container images

The Ananke project uses [Podman](https://podman.io) for containerization. Podman is free, open source, and compatible with [Docker](https://www.docker.com). See [](why-podman) for details on why we use Podman.

Following container images are provided by the Ananke project:
* `ananke-base` (JupyterHub with LTI login and nbgitpuller)
* `ananke-nbgrader` (like `ananke-base` plus LTI integrated nbgrader)

## Documentation

See `doc` subdirectory. There's also an [HTML rendering of the doc](https://gauss.fh-zwickau.de/ananke/doc).

## Contact and contributors

The Ananke project started as a joint project of [Leipzig University of Applied Sciences](https://www.htwk-leipzig.de/en/htwk-leipzig) and [Zwickau University of Applied Sciences](https://www.fh-zwickau.de/english/).

The project team currently consists of:
* [Jens Flemming](https://www.fh-zwickau.de/~jef19jdw)
* [Konrad Schöbel](https://fdit.htwk-leipzig.de/fakultaet-dit/personen/professoren/prof-dr-konrad-schoebel)
* [Marcus Wittig](https://www.fh-zwickau.de/?id=5361)
20 changes: 20 additions & 0 deletions ananke-base-hub/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Container based on Ananke base image

This directory contains shell scripts for creating and removing a container.

**Important**: make this directory your working directory (`cd ...`) before you run the scripts, else the container won't work correctly.

Create and run a container:
```
run.sh
```

Start a shell inside the container:
```
shell.sh
```

Remove the container:
```
remove.sh
```
18 changes: 18 additions & 0 deletions ananke-base-hub/remove.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

CONTAINER_NAME=ananke-base-hub

read -p "Do you really want to remove container $CONTAINER_NAME? Modifications inside the container will be lost! (y/n) " yn
case $yn in
[yY] ) echo "Removing container...";;
[nN] ) echo "Exiting..."; exit;;
* ) echo "Invalid response!"; exit;;
esac

systemctl --user stop container-$CONTAINER_NAME
systemctl --user disable container-$CONTAINER_NAME

rm ~/.config/systemd/user/container-$CONTAINER_NAME.service

podman rm $CONTAINER_NAME

24 changes: 24 additions & 0 deletions ananke-base-hub/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

IMAGE_NAME=ananke-base
CONTAINER_NAME=ananke-base-hub
PORT=8000

echo "Creating container..."

podman create \
-p $PORT:8000 \
--cap-add SYS_ADMIN \
--mount=type=bind,source=runtime/dyn_home,destination=/var/lib/private \
--mount=type=bind,source=runtime/jupyterhub_config.d,destination=/opt/conda/envs/jhub/etc/jupyterhub/jupyterhub_config.d \
-m=8g \
--name=$CONTAINER_NAME \
$IMAGE_NAME

mkdir -p ~/.config/systemd/user
podman generate systemd --restart-policy=always --name $CONTAINER_NAME > ~/.config/systemd/user/container-$CONTAINER_NAME.service
systemctl --user daemon-reload
systemctl --user enable container-$CONTAINER_NAME.service

echo "Starting container..."
systemctl --user start container-$CONTAINER_NAME.service
2 changes: 2 additions & 0 deletions ananke-base-hub/runtime/dyn_home/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions ananke-base-hub/runtime/jupyterhub_config.d/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
20_users.py
30_lms.py
9 changes: 9 additions & 0 deletions ananke-base-hub/runtime/jupyterhub_config.d/00_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# basic hub configuration

c = get_config() # noqa

# hub URL (www.some-domain.org/testhub --> 'testhub/')
c.JupyterHub.base_url = '/'

# log level
c.Application.log_level = 'INFO'
53 changes: 53 additions & 0 deletions ananke-base-hub/runtime/jupyterhub_config.d/10_servers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# configure single-user server behavior for JupyterHub

import sys

c = get_config() # noqa

# multiple servers per user allowed?
c.JupyterHub.allow_named_servers = True

# maximum number of servers per user
c.JupyterHub.named_server_limit_per_user = 3

# on hub restart do not restart user servers and proxy
# (hub restart will be more or less transparent to the users)
c.JupyterHub.cleanup_servers = False
c.JupyterHub.cleanup_proxy = False

# maximum number of concurrent users that can be spawning at a time.
# (if set to 0, no limit is enforced)
c.JupyterHub.concurrent_spawn_limit = 100

# shut down user server(s) on logout?
c.JupyterHub.shutdown_on_logout = False

# resource limits (per user server)
c.SystemdSpawner.unit_extra_properties.update({
'MemoryHigh': '2G', # soft memory limit
'CPUQuota': '200%' # up to 2 cores
})

# empty home directories at shut down
#c.SystemdSpawner.unit_extra_properties.update({
# 'ExecStopPost': 'bash -c "rm -rf /var/lib/private/{USERNAME}/* /var/lib/private/{USERNAME}/.*"',
#})

#-------------------------------------------------------------------------------
# idle-culler
#-------------------------------------------------------------------------------

c.JupyterHub.load_roles.append({
'name': 'jupyterhub-idle-culler-role',
'scopes': ['list:users', 'read:users:activity', 'read:servers', 'delete:servers'],
'services': ['jupyterhub-idle-culler-service'],
})

c.JupyterHub.services.append({
'name': 'jupyterhub-idle-culler-service',
'command': [
sys.executable, '-m', 'jupyterhub_idle_culler',
'--cull-every=43200', # check every 12 hours (in seconds)
'--timeout=259200' # cull if 3 days inactive (in seconds)
]
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# configure user accounts for JupyterHub

c = get_config() # noqa

# username(s) of hub admin(s)
# (login to the hub and look at the URL to get your username)
c.Authenticator.admin_users.add('ADMIN_USERNAME')
10 changes: 10 additions & 0 deletions ananke-base-hub/runtime/jupyterhub_config.d/30_lms.py.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# configure LMS for JupyterHub

c = get_config() # noqa

# configuration data provided by your LMS
base_url = 'URL_OF_LMS'
c.LTI13Authenticator.client_id = 'CLIENT_ID'
c.LTI13Authenticator.issuer = base_url
c.LTI13Authenticator.authorize_url = f'{base_url}/AUTH_LOGIN_PATH'
c.LTI13Authenticator.jwks_endpoint = f'{base_url}/KEY_SET_PATH'
5 changes: 5 additions & 0 deletions ananke-base-hub/shell.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

CONTAINER_NAME=ananke-base-hub

podman exec -it $CONTAINER_NAME /bin/bash
20 changes: 20 additions & 0 deletions ananke-nbgrader-hub/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Container based on Ananke nbgrader image

This directory contains shell scripts for creating and removing a container.

**Important**: make this directory your working directory (`cd ...`) before you run the scripts, else the container won't work correctly.

Create and run a container:
```
run.sh
```

Start a shell inside the container:
```
shell.sh
```

Remove the container:
```
remove.sh
```
18 changes: 18 additions & 0 deletions ananke-nbgrader-hub/remove.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

CONTAINER_NAME=ananke-nbgrader-hub

read -p "Do you really want to remove container $CONTAINER_NAME? Modifications inside the container will be lost! (y/n) " yn
case $yn in
[yY] ) echo "Removing container...";;
[nN] ) echo "Exiting..."; exit;;
* ) echo "Invalid response!"; exit;;
esac

systemctl --user stop container-$CONTAINER_NAME
systemctl --user disable container-$CONTAINER_NAME

rm ~/.config/systemd/user/container-$CONTAINER_NAME.service

podman rm $CONTAINER_NAME

27 changes: 27 additions & 0 deletions ananke-nbgrader-hub/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

IMAGE_NAME=ananke-nbgrader
CONTAINER_NAME=ananke-nbgrader-hub
PORT=8000

echo "Creating container..."

podman create \
-p $PORT:8000 \
--cap-add SYS_ADMIN \
--mount=type=bind,source=runtime/dyn_home,destination=/var/lib/private \
--mount=type=bind,source=runtime/jupyterhub_config.d,destination=/opt/conda/envs/jhub/etc/jupyterhub/jupyterhub_config.d \
--mount=type=bind,source=runtime/home,destination=/home \
--mount=type=bind,source=runtime/kore,destination=/opt/kore/runtime \
--mount=type=bind,source=runtime/nbgrader_exchange,destination=/opt/nbgrader_exchange \
-m=8g \
--name=$CONTAINER_NAME \
$IMAGE_NAME

mkdir -p ~/.config/systemd/user
podman generate systemd --restart-policy=always --name $CONTAINER_NAME > ~/.config/systemd/user/container-$CONTAINER_NAME.service
systemctl --user daemon-reload
systemctl --user enable container-$CONTAINER_NAME.service

echo "Starting container..."
systemctl --user start container-$CONTAINER_NAME.service
2 changes: 2 additions & 0 deletions ananke-nbgrader-hub/runtime/dyn_home/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions ananke-nbgrader-hub/runtime/home/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions ananke-nbgrader-hub/runtime/jupyterhub_config.d/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
20_users.py
30_lms.py
9 changes: 9 additions & 0 deletions ananke-nbgrader-hub/runtime/jupyterhub_config.d/00_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# basic hub configuration

c = get_config() # noqa

# hub URL (www.some-domain.org/testhub --> 'testhub/')
c.JupyterHub.base_url = '/'

# log level
c.Application.log_level = 'INFO'
53 changes: 53 additions & 0 deletions ananke-nbgrader-hub/runtime/jupyterhub_config.d/10_servers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# configure single-user server behavior for JupyterHub

import sys

c = get_config() # noqa

# multiple servers per user allowed?
c.JupyterHub.allow_named_servers = True

# maximum number of servers per user
c.JupyterHub.named_server_limit_per_user = 3

# on hub restart do not restart user servers and proxy
# (hub restart will be more or less transparent to the users)
c.JupyterHub.cleanup_servers = False
c.JupyterHub.cleanup_proxy = False

# maximum number of concurrent users that can be spawning at a time.
# (if set to 0, no limit is enforced)
c.JupyterHub.concurrent_spawn_limit = 100

# shut down user server(s) on logout?
c.JupyterHub.shutdown_on_logout = False

# resource limits (per user server)
c.SystemdSpawner.unit_extra_properties.update({
'MemoryHigh': '2G', # soft memory limit
'CPUQuota': '200%' # up to 2 cores
})

# empty home directories at shut down
#c.SystemdSpawner.unit_extra_properties.update({
# 'ExecStopPost': 'bash -c "rm -rf /var/lib/private/{USERNAME}/* /var/lib/private/{USERNAME}/.*"',
#})

#-------------------------------------------------------------------------------
# idle-culler
#-------------------------------------------------------------------------------

c.JupyterHub.load_roles.append({
'name': 'jupyterhub-idle-culler-role',
'scopes': ['list:users', 'read:users:activity', 'read:servers', 'delete:servers'],
'services': ['jupyterhub-idle-culler-service'],
})

c.JupyterHub.services.append({
'name': 'jupyterhub-idle-culler-service',
'command': [
sys.executable, '-m', 'jupyterhub_idle_culler',
'--cull-every=43200', # check every 12 hours (in seconds)
'--timeout=259200' # cull if 3 days inactive (in seconds)
]
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# configure user accounts for JupyterHub

c = get_config() # noqa

# username(s) of hub admin(s)
# (login to the hub and look at the URL to get your username)
c.Authenticator.admin_users.add('ADMIN_USERNAME')
11 changes: 11 additions & 0 deletions ananke-nbgrader-hub/runtime/jupyterhub_config.d/30_lms.py.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# configure LMS for JupyterHub

c = get_config() # noqa

# configuration data provided by your LMS
base_url = 'URL_OF_LMS'
c.LTI13Authenticator.client_id = 'CLIENT_ID'
c.LTI13Authenticator.issuer = base_url
c.LTI13Authenticator.authorize_url = f'{base_url}/AUTH_LOGIN_PATH'
c.LTI13Authenticator.jwks_endpoint = f'{base_url}/KEY_SET_PATH'
c.LTI13Authenticator.access_token_url = f'{base_url}/ACCESS_TOKEN_PATH'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# configure nbgrader and Kore

c = get_config() # noqa

# make nbgrader log and exchange dir user writable
c.SystemdSpawner.readwrite_paths.extend(['/opt/conda/envs/jhub/share/jupyter/nbgrader.log', '/opt/nbgrader_exchange'])

# Kore config
load_subconfig('/opt/kore/kore_jhub_config.py')
2 changes: 2 additions & 0 deletions ananke-nbgrader-hub/runtime/kore/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
2 changes: 2 additions & 0 deletions ananke-nbgrader-hub/runtime/nbgrader_exchange/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
5 changes: 5 additions & 0 deletions ananke-nbgrader-hub/shell.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

CONTAINER_NAME=ananke-nbgrader-hub

podman exec -it $CONTAINER_NAME /bin/bash
1 change: 1 addition & 0 deletions doc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
html
Loading

0 comments on commit f38d7fd

Please sign in to comment.