diff --git a/.gitignore b/.gitignore index a157c00..7427c59 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # --- This Project ---# data/ logs/ +nbgrader/ .env # Docker secrets diff --git a/jupyterhub/compose.yml b/jupyterhub/compose.yml index 89cc6f3..ca66909 100644 --- a/jupyterhub/compose.yml +++ b/jupyterhub/compose.yml @@ -11,5 +11,6 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./templates:/srv/jupyterhub/templates:ro - ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py - - ./nbgrader_config.py:/srv/nbgrader/courses/nbgrader_config.py - - ../logs/jupyterhub:/var/log/jupyterhub + - ./nbgrader_config.py:/srv/nbgrader/nbgrader_config.py:ro + - ../nbgrader:/srv/nbgrade + - ../nbgrader:/srv/nbgrader diff --git a/jupyterhub/jupyterhub_config.py b/jupyterhub/jupyterhub_config.py index 2252c8d..581a60e 100644 --- a/jupyterhub/jupyterhub_config.py +++ b/jupyterhub/jupyterhub_config.py @@ -86,11 +86,14 @@ c.JupyterHub.template_paths = ["/etc/jupyterhub/templates"] c.JupyterHub.spawner_class = DockerSpawner # Spawn Containers from this Image -c.DockerSpawner.image = os.environ.get("NOTEBOOK_IMAGE", "jupyter/base-notebook:latest") +c.DockerSpawner.image = os.environ.get( + "NOTEBOOK_IMAGE", "jupyter/datascience-notebook:latest" +) # Connect Containers to this network network_name = os.environ.get("DOCKER_NETWORK_NAME", "stack_default") -c.DockerSpawner.use_internal_ip = True # Let docker manage network communications +# Let docker manage network communications +c.DockerSpawner.use_internal_ip = True c.DockerSpawner.network_name = network_name # Explicitly set notebook directory because we'll mounting a volume to it. @@ -128,6 +131,10 @@ c.Spawner.args = [ "--NotebookApp.allow_credentials=True", "--NotebookApp.disable_check_xsrf=True", '--ServerApp.tornado_settings={"headers": {"Access-Control-Allow-Origin": "*"}}', + # NBgrader extensions + "--NotebookApp.nbserver_extensions={'nbgrader.server_extensions.formgrader':True}", + "--NotebookApp.nbserver_extensions={'nbgrader.server_extensions.assignment_list':True}", + "--NotebookApp.nbserver_extensions={'nbgrader.server_extensions.course_list':True}", ] # Security settings @@ -148,3 +155,56 @@ c.JupyterHub.concurrent_spawn_limit = 10 # Allow named servers c.JupyterHub.allow_named_servers = True c.JupyterHub.named_server_limit_per_user = 3 + + +# NBgrader +c.JupyterHub.services = [ + { + "name": "nbgrader-formgrader", + "url": "http://127.0.0.1:9999", + "command": [ + "python", + "-m", + "nbgrader.apps.formgraderapp", + "--FormgradeApp.authenticator_plugin_class=nbgrader.auth.JupyterHubAuthPlugin", + "--FormgradeApp.ip=0.0.0.0", + "--FormgradeApp.port=9999", + f"--FormgradeApp.base_url={base_url}services/nbgrader-formgrader/", + ], + "cwd": "/srv/nbgrader/exchange", + "environment": { + "JUPYTERHUB_API_TOKEN": "", # Will be set automatically + "JUPYTERHUB_API_URL": f"http://jupyterhub:{port}{base_url}hub/api", + "JUPYTERHUB_SERVICE_PREFIX": "/services/nbgrader-formgrader/", + "POSTGRES_USER": p_user, + "POSTGRES_PASSWORD": p_pass, + "POSTGRES_HOST": p_host, + "POSTGRES_DB": p_db, + }, + } +] + +nbgrader_exchange_dir = "/srv/nbgrader/exchange" + +c.DockerSpawner.environment = { + "JUPYTERHUB_API_TOKEN": "", # Will be populated automatically + "JUPYTERHUB_API_URL": f"http://jupyterhub:{port}{base_url}hub/api", + "NBGRADER_EXCHANGE_DIRECTORY": nbgrader_exchange_dir, + "NBGRADER_CACHE_DIRECTORY": "/home/jovyan/.cache/nbgrader", + "NBGRADER_CONFIG_FILE": "/home/jovyan/.jupyter/nbgrader_config.py", +} + +# Configure NBgrader to use the same PostgreSQL database +c.NbGrader.db_url = f"postgresql://{p_user}:{p_pass}@{p_host}:5432/{p_db}" + +# NBgrader course configuration +c.CourseDirectory.root = "/srv/nbgrader" +c.CourseDirectory.course_id = os.environ.get("NBGRADER_COURSE_ID", "default_course") + +# Exchange configuration +c.Exchange.root = nbgrader_exchange_dir +c.Exchange.course_id = os.environ.get("NBGRADER_COURSE_ID", "default_course") + +# Formgrader configuration +c.FormgradeApp.authenticator_plugin_class = "nbgrader.auth.JupyterHubAuthPlugin" +c.FormgradeApp.base_url = f"{base_url}services/nbgrader-formgrader/" diff --git a/jupyterhub/nbgrader_config.py b/jupyterhub/nbgrader_config.py index 0e8e798..54a28d8 100644 --- a/jupyterhub/nbgrader_config.py +++ b/jupyterhub/nbgrader_config.py @@ -1,30 +1,44 @@ -# /srv/nbgrader/nbgrader_config.py +import os + c = get_config() -# 1. Course Configuration -c.CourseDirectory.course_id = "intro-to-python" # Change to your course name -c.CourseDirectory.root = '/srv/nbgrader/courses' +# Database configuration - use the same PostgreSQL as JupyterHub +p_user = os.environ.get("POSTGRES_USER", "") +p_pass = os.environ.get("POSTGRES_PASSWORD", "") +p_host = os.environ.get("POSTGRES_HOST", "postgres") +p_db = os.environ.get("POSTGRES_DB", "jupyterhub") -# 2. Exchange Directory (for submitting/collecting assignments) -c.Exchange.root = '/srv/nbgrader/exchange' -c.Exchange.path_includes_course = True +# NBgrader database URL +c.NbGrader.db_url = f"postgresql://{p_user}:{p_pass}@{p_host}:5432/{p_db}" -# 3. JupyterHub Integration -#c.NbGrader.hub_url = 'http://jupyterhub:8081/hub/api' -#c.NbGrader.hubapi_token = 'your-hub-token' # Generate with: openssl rand -hex 32 +# Course configuration +course_id = os.environ.get("NBGRADER_COURSE_ID", "default_course") +c.CourseDirectory.course_id = course_id +c.CourseDirectory.root = "/srv/nbgrader" -# 4. Database Configuration -c.StudentAssignmentNotebook.database_url = 'sqlite:////srv/nbgrader/nbgrader.sqlite' +# Exchange directory configuration +exchange_dir = os.environ.get("NBGRADER_EXCHANGE_DIRECTORY", "/srv/nbgrader/exchange") +c.Exchange.root = exchange_dir +c.Exchange.course_id = course_id -# 5. Assignment Policies -c.ExecutePreprocessor.timeout = 300 # 5 minutes timeout per cell -c.ClearSolutions.code_stub = "# YOUR CODE HERE" -c.ClearSolutions.text_stub = "YOUR ANSWER HERE" +# Authentication +c.FormgradeApp.authenticator_plugin_class = "nbgrader.auth.JupyterHubAuthPlugin" -# 6. Formgrader UI Settings +# Logging +c.Application.log_level = "INFO" + +# Formgrader settings +c.FormgradeApp.ip = "0.0.0.0" c.FormgradeApp.port = 9999 -c.FormgradeApp.authenticator_class = 'nbgrader.auth.hubauth.HubAuth' -c.FormgradeApp.ip = '0.0.0.0' +c.FormgradeApp.base_url = "/jupyter/services/nbgrader-formgrader/" -# 7. Permissions -c.CourseDirectory.groupshared = True +# Enable tornado debug mode for development +# c.FormgradeApp.tornado_settings = {'debug': True} + +# Assignment settings +c.AssignmentListApp.assignment_dir = "/home/jovyan/assignments" +c.AssignmentListApp.exchange_directory = exchange_dir + +# Grader settings +c.AutogradeApp.force = True +c.GenerateAssignmentApp.force = True