228 lines
7.0 KiB
Python
228 lines
7.0 KiB
Python
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())
|