First complete version

This commit is contained in:
Eric Coissac
2025-10-16 01:07:07 +02:00
parent 57bf9934a3
commit 02b48e75fa
24 changed files with 1265 additions and 110 deletions

43
obijupyterhub/Caddyfile Normal file
View File

@@ -0,0 +1,43 @@
http:// {
root * /srv
file_server
# Proxy pour SFTPGo avec réécriture du chemin
# Proxy pour l'interface Web de SFTPGo
# SFTPGo n'a pas de support natif pour les sous-chemins
# Il faut proxy /web/ et /static/ séparément
handle /web/* {
reverse_proxy http://jupytersftp:8080 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
}
handle /static/* {
reverse_proxy http://jupytersftp:8080 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
}
handle /api/* {
reverse_proxy http://jupytersftp:8080 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
}
handle /sftp/* {
uri strip_prefix /sftp
reverse_proxy http://jupytersftp:8080 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
}
# Proxy vers JupyterHub
handle /jupyter/* {
reverse_proxy http://jupyterhub:8000
}
}

53
obijupyterhub/Dockerfile Normal file
View File

@@ -0,0 +1,53 @@
# ---------- Stage 1 : builder ----------
FROM jupyter/base-notebook:latest AS builder
USER root
# Install system dependencies for R, build tools and Go/Rust
RUN apt-get update && apt-get install -y \
r-base r-base-dev \
libcurl4-openssl-dev libssl-dev libxml2-dev \
build-essential git curl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Install R kernel + useful packages
RUN R -e "install.packages('IRkernel', repos='http://cran.rstudio.com/')" && \
R -e "IRkernel::installspec(user = FALSE)" && \
R -e "install.packages(c('tidyverse','vegan','ade4'), repos='http://cran.rstudio.com/')"
# Install bash kernel
RUN pip install bash_kernel && python -m bash_kernel.install --sys-prefix
# Install obitools4
RUN curl -L https://raw.githubusercontent.com/metabarcoding/obitools4/master/install_obitools.sh | bash
# Install csvkit
RUN pip install csvkit
# Install csvlens via Rust
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y && \
. $HOME/.cargo/env && \
cargo install csvlens
RUN apt-get update && apt-get install -y ruby ruby-dev build-essential \
&& gem install youplot
# Copy csvlens to /usr/local/bin for final use
RUN cp $HOME/.cargo/bin/csvlens /usr/local/bin/
# Set permissions for Jupyter user
RUN mkdir -p /home/${NB_USER}/.local/share/jupyter && \
chown -R ${NB_UID}:${NB_GID} /home/${NB_USER}
COPY start-notebook.sh /usr/local/bin/start-notebook.sh
RUN chmod +x /usr/local/bin/start-notebook.sh
# Switch back to Jupyter user
USER ${NB_UID}:${NB_GID}
WORKDIR /home/${NB_USER}/work
# Environment variables
ENV PATH="/home/${NB_USER}/work/course/bin:${PATH}"
ENV R_LIBS_USER="/home/${NB_USER}/work/R_packages"
ENV R_LIBS_SITE="/home/${NB_USER}/work/course/R_packages:/usr/local/lib/R/site-library:/usr/lib/R/site-library"

View File

@@ -0,0 +1,16 @@
FROM jupyterhub/jupyterhub:latest
# Install DockerSpawner
RUN pip install dockerspawner
# Copy configuration
COPY jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py
# Expose port
EXPOSE 8000
# Working directory
WORKDIR /srv/jupyterhub
# Startup command
CMD ["jupyterhub", "-f", "/srv/jupyterhub/jupyterhub_config.py"]

View File

@@ -0,0 +1,119 @@
services:
jupyterhub:
build:
context: .
dockerfile: Dockerfile.hub
container_name: jupyterhub
hostname: jupyterhub
image: jupyterhub-hub:latest
expose:
- "8000"
volumes:
# Access to Docker socket to spawn student containers
- /var/run/docker.sock:/var/run/docker.sock
# JupyterHub database persistence
- data:/srv/jupyterhub
# The Jupyter user volumes
- users:/volumes
# Mount config file directly (for easy modifications)
- ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py:ro
networks:
- jupyterhub-network
restart: unless-stopped
environment:
# Shared password for all students
JUPYTERHUB_PASSWORD: metabar2025
# Admin password (for installing R packages)
JUPYTERHUB_ADMIN_PASSWORD: admin2025
# Optional environment variables
DOCKER_NOTEBOOK_DIR: /home/jovyan/work
# ---------- Nginx ----------
caddy:
container_name: jupyterhub-caddy
hostname: jupytercaddy
image: caddy:latest
ports:
- "8888:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
- web:/srv # Votre app
networks:
- jupyterhub-network
restart: unless-stopped
# ---------- SFTPGo ----------
sftpgo:
image: drakkan/sftpgo:latest
container_name: jupyterhub-sftpgo
hostname: jupytersftp
command: sftpgo serve --loaddata-from /config/local_config.json --loaddata-clean
expose:
- "2022"
- "8080"
environment:
SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN: true
SFTPGO_DEFAULT_ADMIN_USERNAME: admin
SFTPGO_DEFAULT_ADMIN_PASSWORD: admin2025
SFTPGO_HTTPD__BINDINGS__0__CLIENT_IP_PROXY_HEADER: X-Real-IP
volumes:
- shared:/volumes/shared
- course:/volumes/course
- web:/volumes/web
- ./sftpgo_config.json:/config/local_config.json:ro
networks:
- jupyterhub-network
restart: unless-stopped
networks:
jupyterhub-network:
name: jupyterhub-network
driver: bridge
volumes:
data:
driver: local
driver_opts:
type: none
o: bind
device: ../jupyterhub_volumes/jupyterhub
shared:
driver: local
driver_opts:
type: none
o: bind
device: ../jupyterhub_volumes/shared
course:
driver: local
driver_opts:
type: none
o: bind
device: ../jupyterhub_volumes/course
web:
driver: local
driver_opts:
type: none
o: bind
device: ../jupyterhub_volumes/web
caddy-data:
driver: local
driver_opts:
type: none
o: bind
device: ../jupyterhub_volumes/caddy/data
caddy-config:
driver: local
driver_opts:
type: none
o: bind
device: ../jupyterhub_volumes/caddy/config
users:
driver: local
driver_opts:
type: none
o: bind
device: ../jupyterhub_volumes/users

View File

@@ -0,0 +1,124 @@
import os
import logging
from pathlib import Path
# Base configuration coucou
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
# Enable debug logs
c.JupyterHub.log_level = 'DEBUG'
c.Spawner.debug = True
VOLUMES_BASE_PATH = '/volumes'
# Docker image to use for student containers
c.DockerSpawner.image = 'jupyterhub-student:latest'
# Docker network (create with: docker network create jupyterhub-network)
c.DockerSpawner.network_name = 'jupyterhub-network'
# Connection to OrbStack Docker socket from the hub container
c.DockerSpawner.client_kwargs = {'base_url': 'unix:///var/run/docker.sock'}
# IMPORTANT: Internal URL for communication between containers
# The hub container communicates with user containers via Docker network
c.JupyterHub.hub_ip = '0.0.0.0'
c.JupyterHub.hub_connect_ip = 'jupyterhub'
c.DockerSpawner.environment = {
# R package library in read-only course directory under work/
'R_LIBS_USER': '/home/jovyan/work/R_packages',
'R_LIBS_SITE': '/home/jovyan/work/course/R_packages:/usr/local/lib/R/site-library:/usr/lib/R/site-library'
}
# Network configuration for student containers
c.DockerSpawner.use_internal_ip = True
c.DockerSpawner.network_name = 'jupyterhub-network'
c.DockerSpawner.extra_host_config = {'network_mode': 'jupyterhub-network'}
# Remove containers after disconnection (optional, set to False to keep containers)
c.DockerSpawner.remove = True
# Container naming
c.DockerSpawner.name_template = "jupyter-{username}"
# Volume mounting to persist student data
# Set root to work/ - everything is persistent
notebook_dir = '/home/jovyan/work'
c.DockerSpawner.notebook_dir = notebook_dir
# Personal volume for each student + shared volumes under work/
# Pre-spawn hook to create user directory
async def create_user_dir(spawner):
"""Create user directory if it doesn't exist"""
user_dir = os.path.join(VOLUMES_BASE_PATH, spawner.user.name)
spawner.log.info(f"Ensured user directory exists: {user_dir}")
Path(user_dir).mkdir(parents=True, exist_ok=True)
os.chmod(user_dir, 0o755)
c.Spawner.pre_spawn_hook = create_user_dir
c.DockerSpawner.volumes = {
# Personal volume (persistent) - root directory
'obijupyterhub_shared-{username}' : '/home/jovyan/work',
# Shared volume between all students - under work/
'obijupyterhub_shared': '/home/jovyan/work/shared',
# Shared read-only volume for course files - under work/
'obijupyterhub_course': {
'bind': '/home/jovyan/work/course',
'mode': 'ro' # read-only
}
}
c.DockerSpawner.volume_driver = 'local'
c.DockerSpawner.volume_driver_opts = {
'type': 'none',
'device': '/volumes',
'o': 'bind'
}
# Memory and CPU configuration (adjust according to your needs)
c.DockerSpawner.mem_limit = '2G'
c.DockerSpawner.cpu_limit = 1.0
# User configuration - Simple password authentication for lab
from jupyterhub.auth import DummyAuthenticator
class SimplePasswordAuthenticator(DummyAuthenticator):
"""Simple authenticator with a shared password for everyone"""
def check_allowed(self, username, authentication=None):
"""Check if user is allowed"""
if authentication is None:
return False
provided_password = authentication.get('password', '')
# Admin user with special password
if username == 'admin':
admin_password = os.environ.get('JUPYTERHUB_ADMIN_PASSWORD', 'admin2025')
return provided_password == admin_password
# Regular students with shared password
student_password = os.environ.get('JUPYTERHUB_PASSWORD', 'metabar2025')
return provided_password == student_password
c.JupyterHub.authenticator_class = SimplePasswordAuthenticator
# To create a list of allowed users, uncomment and modify:
# c.Authenticator.allowed_users = {'student1', 'student2', 'student3'}
# Or allow any user with the correct password:
c.Authenticator.allow_all = True
# Admin configuration
c.Authenticator.admin_users = {'admin'}
# Listening port
c.JupyterHub.bind_url = 'http://0.0.0.0:8000/jupyter/'
# Timeout
c.Spawner.start_timeout = 300
c.Spawner.http_timeout = 120
# Post-start hook to create R_packages directory after volumes are mounted
c.DockerSpawner.cmd = ['start-notebook.sh']

View File

@@ -0,0 +1,45 @@
{
"folders": [
{
"name": "shared_data",
"mapped_path": "/volumes/shared",
"description": "Dossier partagé entre utilisateurs",
"filesystem": {
"provider": 0
}
},
{
"name": "web_site",
"mapped_path": "/volumes/web",
"description": "Site web static",
"filesystem": {
"provider": 0
}
}
],
"users": [
{
"username": "admin",
"password": "admin2025",
"status": 1,
"home_dir": "/volumes/course",
"permissions": {
"/": ["*"]
},
"virtual_folders": [
{
"name": "shared_data",
"virtual_path": "/shared",
"quota_size": -1,
"quota_files": -1
},
{
"name": "web_site",
"virtual_path": "/web",
"quota_size": -1,
"quota_files": -1
}
]
}
]
}

View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -e
# Function to create directory
create_r_packages_dir() {
local max_attempts=10
local attempt=1
while [ $attempt -le $max_attempts ]; do
if [ -d "/home/jovyan/work" ]; then
mkdir -p /home/jovyan/work/R_packages
echo "R_packages directory created successfully"
return 0
fi
echo "Waiting for work directory to be mounted (attempt $attempt/$max_attempts)..."
sleep 1
attempt=$((attempt + 1))
done
echo "Warning: Could not verify work directory mount"
return 1
}
# Create R_packages directory
create_r_packages_dir
# Start the single-user server
exec jupyterhub-singleuser "$@"