Files
jupyter/jupyterhub/jupyterhub_config.py
2025-09-15 16:19:07 +02:00

207 lines
6.1 KiB
Python

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']}@{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/base-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