import os import sys from dockerspawner import DockerSpawner from jupyterhub.utils import random_port from nativeauthenticator import NativeAuthenticator #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 import subprocess 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, 'DerGrumpf'} 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 c.JupyterHub.spawner_class = DockerSpawner c.Spawner.ip = '0.0.0.0' c.DockerSpawner.image = os.environ.get('NOTEBOOK_IMAGE', 'jupyter/scipy-notebook:latest') '''network_name = os.environ.get('DOCKER_NETWORK_NAME', 'stack_default') c.DockerSpawner.network_name = network_name c.DockerSpawner.extra_host_config = { 'network_mode': network_name } c.DockerSpawner.remove = True c.DockerSpawner.debug = True c.DockerSpawner.default_url = os.environ.get('SPAWNER_DEFAULT_URL', '/lab') c.DockerSpawner.use_internal_ip = True # Resources c.DockerSpawner.mem_limit = os.environ.get('NOTEBOOK_MEMORY_LIMIT', '500M') c.DockerSpawner.cpu_limit = float(os.environ.get('NOTEBOOK_CPU_LIMIT', '1.0')) # Volume c.DockerSpawner.volumes = { '/var/run/docker.sock': '/var/run/docker.sock' # './data/jupyter/users/{username}': '/home/jovyan/work', # './data/jupyter/nbgrader/exchange': '/srv/nbgrader/exchange', # './data/jupyter/nbgrader/courses': '/srv/nbgrader/courses', } c.DockerSpawner.notebook_dir = '/home/jovyan/work' # Container Environment c.DockerSpawner.environment = { 'GRANT_SUDO': '0', 'JUPYTER_ENABLE_LAB': os.environ.get('ENABLE_LAB', '1'), 'JUPYTERHUB_SINGLEUSER_APP': 'jupyter_server.serverapp.ServerApp' } #c.DockerSpawner.hub_ip_connect = os.environ.get('HUB_IP', 'jupyterhub') #c.DockerSpawner.hub_port_connect = 8081 def pre_spawn_hook(spawner): """Create user directories before spawning""" username = spawner.user.name user_dir = f"./data/jupyter/users/{username}" import os import stat if not os.path.exists(user_dir): os.makedirs(user_dir, mode=0o755, exist_ok=True) os.chown(user_dir, 1000, 1000) # TODO Nbgrader dirs c.DockerSpawner.pre_spawn_hook = pre_spawn_hook ''' ''' # 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 print("JupyterHub configuration loaded successfully!") print(f"Base URL: {c.JupyterHub.base_url}") print(f"Bind URL: {c.JupyterHub.bind_url}") print(f"Database URL: {c.JupyterHub.db_url}") print(f"Docker Network: {c.DockerSpawner.network_name}") print(f"Docker Image: {c.DockerSpawner.image}") print("Postgres available", is_service_available_cmd("postgres", 5432)) with open("/srv/jupyterhub/cookie_secret") as f: print(f.readlines())