diff --git a/Dockerfile b/Dockerfile index 039bd7d..cc5f2a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,9 @@ RUN apt-get update && apt-get install -y \ libcurl4-openssl-dev \ libssl-dev \ libxml2-dev \ + git \ + build-essential \ + curl \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Install R kernel for Jupyter (as root) @@ -16,16 +19,29 @@ RUN R -e "install.packages('IRkernel', repos='http://cran.rstudio.com/')" && \ R -e "IRkernel::installspec(user = FALSE)" # Install some useful R packages for labs -RUN R -e "install.packages(c('ggplot2', 'dplyr', 'tidyr', 'readr'), repos='http://cran.rstudio.com/')" +RUN R -e "install.packages(c('tidyverse', 'vegan', 'ade4'), repos='http://cran.rstudio.com/')" # Install bash kernel (as root also) RUN pip install bash_kernel && \ python -m bash_kernel.install --sys-prefix +# Install obitools4 (written in Go) +RUN curl -L https://raw.githubusercontent.com/metabarcoding/obitools4/master/install_obitools.sh | bash + +# Install csvkit +RUN pip install csvkit + +# Install csvlens +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y \ + && . $HOME/.cargo/env \ + && cargo install csvlens \ + && mv $HOME/.cargo/bin/csvlens /usr/local/bin/ \ + && rm -rf $HOME/.cargo $HOME/.rustup + # Create necessary directories with proper permissions RUN mkdir -p /home/${NB_USER}/.local/share/jupyter && \ chown -R ${NB_UID}:${NB_GID} /home/${NB_USER} USER ${NB_UID} -WORKDIR /home/${NB_USER} \ No newline at end of file +WORKDIR /home/${NB_USER} diff --git a/Readme.md b/Readme.md index 0250b0f..5b9f631 100644 --- a/Readme.md +++ b/Readme.md @@ -113,6 +113,71 @@ Each student will see these directories in their JupyterLab: - **`work/`** : Personal workspace (persistent, private) - **`shared/`** : Shared workspace between all students (read/write) - **`course/`** : Course files (read-only, you deposit files) + - **`course/R_packages/`** : Shared R packages (read-only for students, only admin can install) + +### User Accounts + +**Admin Account:** +- Username: `admin` +- Password: `admin2025` (change in docker-compose.yml: `JUPYTERHUB_ADMIN_PASSWORD`) +- Can write to `course/` directory + +**Student Accounts:** +- Username: any name +- Password: `metabar2025` (change in docker-compose.yml: `JUPYTERHUB_PASSWORD`) +- Read-only access to `course/` directory + +### Installing R Packages (Admin Only) + +**From your Mac (recommended):** + +```bash +chmod +x install-r-packages-admin.sh + +# Install packages +./install-r-packages-admin.sh reshape2 plotly knitr +``` + +This script: +- Installs packages in the `course/R_packages/` directory +- All students can use them (read-only) +- No need to rebuild the image + +**From admin notebook:** + +Login as `admin` and create an R notebook: + +```r +# Install packages in course directory (admin only) +course_lib <- "/home/jovyan/course/R_packages" +dir.create(course_lib, recursive = TRUE, showWarnings = FALSE) + +install.packages(c('reshape2', 'plotly', 'knitr'), + lib = course_lib, + repos = 'http://cran.rstudio.com/') +``` + +Note: Admin account has write access to the course directory. + +### Using R Packages (Students) + +Students simply load packages normally: +```r +library(reshape2) # Loads from course/R_packages/ automatically +library(plotly) +``` + +R automatically finds packages in `/home/jovyan/course/R_packages/` thanks to the `R_LIBS_USER` environment variable. + +### List Available Packages + +```r +# List all available packages +installed.packages()[,"Package"] + +# Or check course packages specifically +list.files("/home/jovyan/course/R_packages") +``` ### Deposit Files for Course @@ -254,4 +319,4 @@ docker-compose down -v docker rmi jupyterhub-hub jupyterhub-student # Then rebuild everything ./start-jupyterhub.sh -``` +``` \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yml similarity index 88% rename from docker-compose.yaml rename to docker-compose.yml index 8ff08d1..6d22d71 100644 --- a/docker-compose.yaml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: container_name: jupyterhub image: jupyterhub-hub:latest ports: - - "8000:8000" + - "8888:8000" volumes: # Access to Docker socket to spawn student containers - /var/run/docker.sock:/var/run/docker.sock @@ -20,6 +20,8 @@ services: 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 diff --git a/install_packages.sh b/install_packages.sh new file mode 100644 index 0000000..57bd94a --- /dev/null +++ b/install_packages.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Script to install R packages in the course directory (admin only) +# Usage: ./install-r-packages.sh package1 package2 package3 + +set -e + +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +echo -e "${BLUE}πŸ“¦ R Package Installer (Admin)${NC}" +echo "================================" +echo "" + +# Check if packages are provided +if [ $# -eq 0 ]; then + echo -e "${RED}❌ Error: No packages specified${NC}" + echo "" + echo "Usage:" + echo " ./install-r-packages.sh package1 package2 package3" + echo "" + echo "Example:" + echo " ./install-r-packages.sh reshape2 plotly knitr" + exit 1 +fi + +# Build package list +PACKAGES=$(IFS=,; echo "$*") +R_PACKAGES=$(echo "$PACKAGES" | sed "s/,/', '/g") + +echo -e "${YELLOW}πŸ“‹ Packages to install: ${R_PACKAGES}${NC}" +echo "" + +# Create a temporary directory for installation +TEMP_DIR=$(mktemp -d) +echo -e "${BLUE}πŸ“ Creating temporary directory: ${TEMP_DIR}${NC}" + +# Install packages in temporary directory +echo -e "${BLUE}πŸ”¨ Installing R packages...${NC}" +docker run --rm \ + --user root \ + -v "${TEMP_DIR}:/temp_packages" \ + jupyterhub-student:latest \ + R -e "install.packages(c('${R_PACKAGES}'), lib='/temp_packages', repos='http://cran.rstudio.com/')" || { + echo -e "${RED}❌ Failed to install packages${NC}" + rm -rf "${TEMP_DIR}" + exit 1 +} + +# Copy to course volume +echo "" +echo -e "${BLUE}πŸ’Ύ Copying to course volume...${NC}" +docker run --rm \ + -v jupyterhub-course:/target \ + -v "${TEMP_DIR}:/source" \ + alpine sh -c "mkdir -p /target/R_packages && cp -r /source/* /target/R_packages/" + +# Clean up +rm -rf "${TEMP_DIR}" + +# List installed packages +echo "" +echo -e "${GREEN}βœ… Installation complete!${NC}" +echo "" +echo -e "${BLUE}πŸ“¦ Installed packages in course/R_packages:${NC}" +docker run --rm \ + -v jupyterhub-course:/course \ + alpine ls -1 /course/R_packages/ + +echo "" +echo -e "${YELLOW}ℹ️ Students need to restart their R kernels to use new packages.${NC}" \ No newline at end of file diff --git a/jupyterhub_config.py b/jupyterhub_config.py index 4bf7668..ba65a7b 100644 --- a/jupyterhub_config.py +++ b/jupyterhub_config.py @@ -33,19 +33,19 @@ c.DockerSpawner.remove = True c.DockerSpawner.name_template = "jupyter-{username}" # Volume mounting to persist student data -# Set root to /home/jovyan to see all directories -notebook_dir = '/home/jovyan' +# Set root to work/ - everything is persistent +notebook_dir = '/home/jovyan/work' c.DockerSpawner.notebook_dir = notebook_dir -# Personal volume for each student + shared volume +# Personal volume for each student + shared volumes under work/ c.DockerSpawner.volumes = { - # Personal volume (persistent) - mounted in work/ + # Personal volume (persistent) - root directory 'jupyterhub-user-{username}': '/home/jovyan/work', - # Shared volume between all students - 'jupyterhub-shared': '/home/jovyan/shared', - # Shared read-only volume for course files (optional) + # Shared volume between all students - under work/ + 'jupyterhub-shared': '/home/jovyan/work/shared', + # Shared read-only volume for course files - under work/ 'jupyterhub-course': { - 'bind': '/home/jovyan/course', + 'bind': '/home/jovyan/work/course', 'mode': 'ro' # read-only } } @@ -54,6 +54,23 @@ c.DockerSpawner.volumes = { c.DockerSpawner.mem_limit = '2G' c.DockerSpawner.cpu_limit = 1.0 +# Environment variables for student containers +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', + # Path to R packages in read-only + 'PATH': '/home/jovyan/work/course/bin:${PATH}' +} + +# Create user R lib directory +#async def create_user_hierarchy(spawner): +# cmd = "mkdir -p /home/jovyan/work/R_packages && chown jovyan:jovyan /home/jovyan/work/R_packages" +# spawner.extra_create_kwargs.update({'command': f"/bin/bash -c '{cmd} && start-notebook.sh'"}) + +#c.Spawner.pre_spawn_hook = create_user_r_libs + + # User configuration - Simple password authentication for lab from jupyterhub.auth import DummyAuthenticator @@ -65,12 +82,16 @@ class SimplePasswordAuthenticator(DummyAuthenticator): if authentication is None: return False - # Get password from environment variable - expected_password = os.environ.get('JUPYTERHUB_PASSWORD', 'metabar2025') provided_password = authentication.get('password', '') - # Check password - return provided_password == expected_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 @@ -125,7 +146,7 @@ c.DockerSpawner.name_template = "jupyter-{username}" # Montage de volumes pour persister les donnΓ©es des Γ©tudiants # DΓ©finir la racine Γ  /home/jovyan pour voir tous les dossiers -notebook_dir = '/home/jovyan' +notebook_dir = '/home/jovyan/work' c.DockerSpawner.notebook_dir = notebook_dir # Volume personnel pour chaque Γ©tudiant + volume partagΓ© @@ -133,10 +154,10 @@ c.DockerSpawner.volumes = { # Volume personnel (persistant) - montΓ© dans work/ 'jupyterhub-user-{username}': '/home/jovyan/work', # Volume partagΓ© entre tous les Γ©tudiants - 'jupyterhub-shared': '/home/jovyan/shared', + 'jupyterhub-shared': '/home/jovyan/work/shared', # Volume partagΓ© en lecture seule pour les fichiers du cours (optionnel) 'jupyterhub-course': { - 'bind': '/home/jovyan/course', + 'bind': '/home/jovyan/work/course', 'mode': 'ro' # read-only } } diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 index 9d95ed5..29b4fa2 --- a/setup.sh +++ b/setup.sh @@ -61,16 +61,20 @@ if docker ps | grep -q jupyterhub; then echo -e "${GREEN}βœ… JupyterHub is running!${NC}" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo -e "${GREEN}🌐 JupyterHub available at: http://localhost:8000${NC}" + echo -e "${GREEN}🌐 JupyterHub available at: http://localhost:8888${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "πŸ“ Password: metabar2025" echo "πŸ‘₯ Students can connect with any username" echo "" + echo "πŸ”‘ Admin account:" + echo " Username: admin" + echo " Password: admin2025" + echo "" echo "πŸ“‚ Each student will have access to:" - echo " - work/ : personal workspace" - echo " - shared/ : shared workspace" - echo " - course/ : course files (read-only)" + echo " - work/ : personal workspace (everything here is saved)" + echo " - work/shared/ : shared workspace" + echo " - work/course/ : course files (read-only)" echo "" echo "πŸ” To view logs: docker-compose logs -f jupyterhub" echo "πŸ›‘ To stop: docker-compose down"