import os import sys from dockerspawner import DockerSpawner from jupyterhub.utils import random_port from nativeauthenticator import NativeAuthenticator import subprocess # from nbgrader.auth import JupyterHubAuthPlugin from traitlets import Unicode, Bool, Int, Float import secrets import string from datetime import datetime, timedelta from typing import Optional def generate_api_token( length: int = 32, prefix: Optional[str] = None, ) -> dict: """ Generate a secure random API token with optional prefix and expiration. Args: length: Length of the token (default: 32) prefix: Optional prefix for the token (e.g., 'hub_') expiration_days: Optional expiration in days from now Returns: - token: The generated token """ # Generate cryptographically secure random string alphabet = string.ascii_letters + string.digits token = "".join(secrets.choice(alphabet) for _ in range(length)) # Add prefix if specified if prefix: token = f"{prefix}{token}" return token def is_service_available_cmd(host, port): """Check service using system commands""" try: subprocess.run( ["nc", "-z", host, str(port)], check=True, timeout=3, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) return True except (subprocess.TimeoutExpired, subprocess.CalledProcessError): return False # Configuration file for JupyterHub c = get_config() # Base URL configuration # The new way, dont use ip/port/bind_url # attributes it throws a warning and is deprecated port = int(os.environ.get("JUPYTERHUB_PORT", "8000")) base_url = os.environ.get("JUPYTERHUB_BASE_URL", "/jupyter/") c.JupyterHub.bind_url = f"http://0.0.0.0:{port}{base_url}" # Database configuration c.JupyterHub.db_url = f"postgresql://{os.environ['POSTGRES_USER']}:{os.environ['POSTGRES_PASSWORD']}@stack-{os.environ['POSTGRES_HOST']}:5432/{os.environ['POSTGRES_DB']}" # c.JupyterHub.db_url = 'sqlite:///jupyterhub.sqlite' # Authenticator configuration - NativeAuthenticator c.JupyterHub.authenticator_class = NativeAuthenticator # Native Authenticator settings # c.NativeAuthenticator.create_system_users = True c.NativeAuthenticator.minimum_password_length = int( os.environ.get("NATIVE_AUTH_MIN_PASSWORD_LENGTH", "8") ) c.NativeAuthenticator.check_common_password = True c.NativeAuthenticator.enable_signup = True c.NativeAuthenticator.ask_email_on_signup = False c.NativeAuthenticator.allow_2fa = False # Admin configuration admin_user = os.environ.get("JUPYTERHUB_ADMIN_USER", "admin") c.Authenticator.admin_users = {admin_user} c.Authenticator.any_allow_config = True c.Authenticator.allow_all = True # Custom logo and templates c.JupyterHub.logo_file = "/srv/jupyterhub/templates/logo.png" c.JupyterHub.template_paths = ["/etc/jupyterhub/templates"] # Hub environment for containers # Spawn Single-User-Servers as Docker Containers c.JupyterHub.spawner_class = DockerSpawner # Spawn Containers from this Image c.DockerSpawner.image = os.environ.get( "NOTEBOOK_IMAGE", "jupyter/scipy-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 c.DockerSpawner.network_name = network_name # Explicitly set notebook directory because we'll mounting a volume to it. notebook_dir = os.environ.get("DOCKER_NOTEBOOK_DIR", "/home/jovyan/work") c.DockerSpawner.notebook_dir = notebook_dir # Mount real User docker volume to the host c.DockerSpawner.volumes = {"jupyterhub-user-{username}": notebook_dir} # Remove Containers once there stopped c.DockerSpawner.remove = True # For Debbugging (passed to spawned Containers) ds_debug = True if int(os.environ.get("DOCKER_SPAWNER_DEBUG", "0")) else False c.DockerSpawner.debug = ds_debug # Dont mess with this # The Spawner sits inside a docker container which manages # everything network related c.Spawner.ip = "0.0.0.0" # Set Resource Limits c.DockerSpawner.mem_limit = os.environ.get("NOTEBOOK_MEMORY_LIMIT", "500M") c.DockerSpawner.cpu_limit = float(os.environ.get("NOTEBOOK_CPU_LIMIT", "1.0")) """ # Services configuration for NBGrader c.JupyterHub.services = [ { 'name': 'nbgrader-formgrader', 'url': 'http://127.0.0.1:9999', 'command': [ 'jupyter-nbgrader', '--port=9999', '--no-browser', '--log-level=INFO' ], 'cwd': '/srv/jupyterhub/courses', 'user': 'root', 'environment': { 'PYTHONPATH': '/srv/nbgrader', 'NBGRADER_CONFIG_FILE': '/srv/jupyterhub/nbgrader/nbgrader_config.py' } } ] """ """ # NBGrader configuration c.JupyterHub.load_groups = { 'nbgrader-instructors': ['instructor'], 'nbgrader-students': [] } """ """ # NBGrader Config c.JupyterHub.services = [ { 'name': 'nbgrader-formgrader', 'url': 'http://127.0.0.1:9999', 'api_token': generate_api_token(prefix='nbgrader'), 'command': [ 'python', '-m', 'nbgrader', 'formgrader', #'--port=9999', #'--no-browser', '--log-level=INFO', #'--base_url=/jupyter/services/nbgrader-formgrader', '--debug' ], 'environment': { 'JUPYTERHUB_SERVICE_URL': 'http://127.0.0.1:8081/jupyter/hub/api', 'NBGRADER_CONFIG_FILE': '/srv/nbgrader/nbgrader_config.py', 'PYTHONPATH': '/srv/nbgrader' }, 'cwd': '/srv/nbgrader/courses', 'user': 'root' } ] """ # Security settings c.JupyterHub.cookie_secret_file = "/srv/jupyterhub/cookie_secret" c.ConfigurableHTTPProxy.auth_token = os.environ.get("JUPYTERHUB_AUTH_TOKEN", "") # Logging c.JupyterHub.log_level = os.environ.get("LOG_LEVEL", "INFO") # Shutdown settings c.JupyterHub.shutdown_on_logout = False c.JupyterHub.cleanup_servers = True # Timeout settings c.JupyterHub.active_server_limit = 0 c.JupyterHub.concurrent_spawn_limit = 10 # Allow named servers c.JupyterHub.allow_named_servers = True c.JupyterHub.named_server_limit_per_user = 3