214 lines
7.2 KiB
Python
214 lines
7.2 KiB
Python
import os
|
|
from dockerspawner import DockerSpawner
|
|
from nativeauthenticator import NativeAuthenticator
|
|
|
|
import secrets
|
|
import string
|
|
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
|
|
|
|
|
|
# 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}"
|
|
|
|
# Mission Critical this should be the service name not the container name
|
|
# without this option containers cant reach the api endpoint
|
|
# this is due to nginx external rounting and might help
|
|
# migration this project to a cluster setup
|
|
c.JupyterHub.hub_connect_ip = "jupyterhub"
|
|
|
|
# Database configuration
|
|
p_user = os.environ["POSTGRES_USER"]
|
|
p_pass = os.environ["POSTGRES_PASSWORD"]
|
|
p_host = os.environ["POSTGRES_HOST"]
|
|
p_db = os.environ["POSTGRES_DB"]
|
|
c.JupyterHub.db_url = f"postgresql://{p_user}:{p_pass}@{p_host}:5432/{p_db}"
|
|
# c.JupyterHub.db_url = 'sqlite:///jupyterhub.sqlite'
|
|
|
|
# Authenticator configuration - NativeAuthenticator
|
|
c.JupyterHub.authenticator_class = NativeAuthenticator
|
|
|
|
# Native Authenticator settings
|
|
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")
|
|
|
|
# Spawn this "version" of the Notebook
|
|
c.DockerSpawner.default_url = os.environ.get("SPAWNER_DEFAULT_URL", "/tree")
|
|
|
|
# Connect Containers to this network
|
|
network_name = os.environ.get("DOCKER_NETWORK_NAME", "stack_default")
|
|
# 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.
|
|
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"))
|
|
|
|
# Fixing CORS because Nginx
|
|
# this isn't a security risk because the container is served behind nginx
|
|
# this only affects the traffic within the docker network itself
|
|
c.Spawner.args = [
|
|
"--ServerApp.allow_origin=*",
|
|
"--ServerApp.allow_credentials=True",
|
|
"--ServerApp.disable_check_xsrf=True",
|
|
"--NotebookApp.allow_origin=*",
|
|
"--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
|
|
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
|
|
|
|
|
|
# NBgrader
|
|
|
|
nbgrader_exchange_dir = "/srv/nbgrader/exchange"
|
|
|
|
"""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,
|
|
},
|
|
}
|
|
]
|
|
|
|
|
|
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/"
|