First complete version
This commit is contained in:
43
obijupyterhub/Caddyfile
Normal file
43
obijupyterhub/Caddyfile
Normal 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
53
obijupyterhub/Dockerfile
Normal 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"
|
||||
|
||||
16
obijupyterhub/Dockerfile.hub
Normal file
16
obijupyterhub/Dockerfile.hub
Normal 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"]
|
||||
119
obijupyterhub/docker-compose.yml
Normal file
119
obijupyterhub/docker-compose.yml
Normal 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
|
||||
124
obijupyterhub/jupyterhub_config.py
Normal file
124
obijupyterhub/jupyterhub_config.py
Normal 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']
|
||||
45
obijupyterhub/sftpgo_config.json
Normal file
45
obijupyterhub/sftpgo_config.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
28
obijupyterhub/start-notebook.sh
Normal file
28
obijupyterhub/start-notebook.sh
Normal 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 "$@"
|
||||
Reference in New Issue
Block a user