This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/cybershuttle-staging by this
push:
new cadfa1637c research hub configurations and compose files
cadfa1637c is described below
commit cadfa1637c992aa98286b10d96f795b9246a760f
Author: lahiruj <[email protected]>
AuthorDate: Sun Mar 30 18:15:09 2025 -0400
research hub configurations and compose files
---
.../research-hub/compose/Dockerfile | 21 ++++
.../compose/custom_templates/login.html | 52 ++++++++
.../research-hub/compose/docker-compose.yaml | 24 ++++
.../research-hub/compose/jupyterhub_config.py | 138 +++++++++++++++++++++
.../research-hub/compose/user-container/Dockerfile | 12 ++
.../compose/user-container/bootstrap.sh | 1 +
.../compose/user-container/build-container.sh | 2 +
.../research-hub/compose/user-container/init.sh | 33 +++++
8 files changed, 283 insertions(+)
diff --git a/modules/research-framework/research-hub/compose/Dockerfile
b/modules/research-framework/research-hub/compose/Dockerfile
new file mode 100644
index 0000000000..91ea866fc6
--- /dev/null
+++ b/modules/research-framework/research-hub/compose/Dockerfile
@@ -0,0 +1,21 @@
+FROM jupyterhub/jupyterhub:3.0
+
+RUN pip install oauthenticator requests pyjwt dockerspawner
jupyterhub-idle-culler
+RUN apt-get update && \
+ apt-get install -y git && \
+ pip install oauthenticator requests pyjwt dockerspawner
jupyterhub-idle-culler ipywidgets
+
+
+COPY jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py
+
+COPY custom_templates /srv/jupyterhub/custom_templates
+
+RUN mkdir -p /home/jovyan/notebooks && \
+ chown -R 1000:100 /home/jovyan/notebooks
+
+ENV JUPYTERHUB_CONFIG=/srv/jupyterhub/jupyterhub_config.py
+ENV PYTHONPATH=/srv/jupyterhub
+
+EXPOSE 8000
+
+CMD ["jupyterhub"]
\ No newline at end of file
diff --git
a/modules/research-framework/research-hub/compose/custom_templates/login.html
b/modules/research-framework/research-hub/compose/custom_templates/login.html
new file mode 100644
index 0000000000..faf6fcfa52
--- /dev/null
+++
b/modules/research-framework/research-hub/compose/custom_templates/login.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>CyberShuttle Hub Login</title>
+ <link rel="icon"
href="https://dev.cybershuttle.org/static/images/airavata-logo.png">
+ <link rel="stylesheet" href="{{ static_url("css/style.min.css") }}">
+ <style>
+ body {
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
+ background-color: #f7f9fc;
+ color: #333;
+ text-align: center;
+ padding-top: 80px;
+ }
+ .container {
+ display: inline-block;
+ padding: 50px;
+ border-radius: 8px;
+ background-color: #ffffff;
+ box-shadow: 0 3px 8px rgba(0,0,0,0.1);
+ }
+ img {
+ width: 70px;
+ margin-bottom: 20px;
+ }
+ h1 {
+ font-size: 26px;
+ margin-bottom: 20px;
+ }
+ a.btn-login {
+ display: inline-block;
+ background-color: #0d6efd;
+ color: #ffffff;
+ padding: 10px 20px;
+ border-radius: 5px;
+ text-decoration: none;
+ font-size: 16px;
+ }
+ a.btn-login:hover {
+ background-color: #0b5ed7;
+ }
+ </style>
+</head>
+<body>
+<div class="container">
+ <img src="https://dev.cybershuttle.org/static/images/airavata-logo.png"
alt="CyberShuttle Logo"/>
+ <h1>Welcome to Dev CyberShuttle Hub</h1>
+ <a class="btn-login" href="{{ authenticator_login_url }}">Login with OAuth
2.0</a>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git
a/modules/research-framework/research-hub/compose/docker-compose.yaml
b/modules/research-framework/research-hub/compose/docker-compose.yaml
new file mode 100644
index 0000000000..ec8936f5fb
--- /dev/null
+++ b/modules/research-framework/research-hub/compose/docker-compose.yaml
@@ -0,0 +1,24 @@
+version: "3.8"
+services:
+ jupyterhub:
+ build: .
+ container_name: jupyterhub-sample
+ ports:
+ - "8000:8000"
+ environment:
+ OAUTH_CLIENT_ID: "cs-jupyterlab"
+ OAUTH_CLIENT_SECRET: "CHANGE_ME"
+ JUPYTERHUB_CRYPT_KEY:
"a99323294a5d6f9b1d0e7e33450dff44db664264231b985e069c6eba8f9a3e09"
+ DOCKER_NETWORK_NAME: jupyterhub_network
+ DOCKER_NOTEBOOK_IMAGE: cybershuttle/dev_jupyterlab-base
+ volumes:
+ - ./jupyterlab:/home/jovyan
+ - ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py
+ - /var/run/docker.sock:/var/run/docker.sock
+ restart: always
+ networks:
+ - jupyterhub_network
+
+networks:
+ jupyterhub_network:
+ name: jupyterhub_network
\ No newline at end of file
diff --git
a/modules/research-framework/research-hub/compose/jupyterhub_config.py
b/modules/research-framework/research-hub/compose/jupyterhub_config.py
new file mode 100644
index 0000000000..5e5dcb46a0
--- /dev/null
+++ b/modules/research-framework/research-hub/compose/jupyterhub_config.py
@@ -0,0 +1,138 @@
+import os
+import re
+import sys
+from dockerspawner import DockerSpawner
+from oauthenticator.generic import GenericOAuthenticator
+
+# Authenticator Configuration
+c.JupyterHub.authenticator_class = GenericOAuthenticator
+c.GenericOAuthenticator.client_id = os.getenv('OAUTH_CLIENT_ID')
+c.GenericOAuthenticator.client_secret = os.getenv('OAUTH_CLIENT_SECRET')
+c.GenericOAuthenticator.oauth_callback_url =
'https://hub.dev.cybershuttle.org/hub/oauth_callback'
+c.GenericOAuthenticator.authorize_url =
'https://auth.dev.cybershuttle.org/realms/default/protocol/openid-connect/auth'
+c.GenericOAuthenticator.token_url =
'https://auth.dev.cybershuttle.org/realms/default/protocol/openid-connect/token'
+c.GenericOAuthenticator.userdata_url =
'https://auth.dev.cybershuttle.org/realms/default/protocol/openid-connect/userinfo'
+c.GenericOAuthenticator.scope = ['openid', 'profile', 'email']
+c.GenericOAuthenticator.username_claim = 'email'
+
+# User Permissions
+c.Authenticator.enable_auth_state = True
+c.GenericOAuthenticator.allow_all = True
+c.Authenticator.admin_users = {'[email protected]'}
+
+
+# Custom Spawner
+class CustomDockerSpawner(DockerSpawner):
+
+ def _options_form_default(self):
+ return ""
+
+ def options_from_form(self, formdata):
+ options = {}
+
+ if hasattr(self, 'handler') and self.handler:
+ qs_args = self.handler.request.arguments # eg. {'git':
[b'https://github...'], 'dataPath': [b'/home/...']}
+ if 'git' in qs_args:
+ options['git'] = qs_args['git'][0].decode('utf-8')
+ if 'dataPath' in qs_args:
+ options['dataPath'] = qs_args['dataPath'][0].decode('utf-8')
+
+ return options
+
+ def get_image(self):
+ user_requested = self.user_options.get("image", "base")
+ default_image = "cybershuttle/dev_jupyterlab-base"
+ return self.allowed_images.get(user_requested, default_image)
+
+ def sanitize_name(self, name):
+ """Docker safe volume/container names."""
+ return re.sub(r"[^a-zA-Z0-9_.-]", "_", name)
+
+ async def start(self):
+ # Create a unique volume name keyed by (username + servername).
+ # If the user spawns again with the same (servername), it will reuse
the same volume.
+ safe_user = self.sanitize_name(self.user.name)
+ safe_srv = self.sanitize_name(self.name) or "default"
+
+ vol_name = f"jupyterhub-vol-{safe_user}-{safe_srv}"
+
+ volumes_dict = {
+ vol_name: "/home/jovyan/work"
+ }
+
+ # If dataPath was provided, mount it read-only
+ data_path = self.user_options.get("dataPath")
+ if data_path:
+ volumes_dict[data_path] = {
+ 'bind': f"/home/jovyan/shared_data_tmp",
+ 'mode': 'ro'
+ }
+
+ self.volumes = volumes_dict
+
+ git_url = self.user_options.get("git")
+ if git_url:
+ if not hasattr(self, "environment"):
+ self.environment = {}
+ self.environment["GIT_URL"] = git_url
+
+ return await super().start()
+
+
+# Spawner Configuration
+c.JupyterHub.allow_named_servers = True
+c.JupyterHub.named_server_limit_per_user = 5
+c.JupyterHub.spawner_class = CustomDockerSpawner
+c.DockerSpawner.notebook_dir = '/home/jovyan/work'
+c.DockerSpawner.default_url = "/lab"
+
+c.DockerSpawner.environment = {
+ 'CHOWN_HOME': 'no',
+ 'CHOWN_HOME_OPTS': '',
+}
+c.DockerSpawner.extra_create_kwargs = {'user': 'root'}
+c.DockerSpawner.use_internal_ip = True
+c.DockerSpawner.network_name = os.getenv('DOCKER_NETWORK_NAME',
'jupyterhub_network')
+c.DockerSpawner.image = os.environ.get('DOCKER_NOTEBOOK_IMAGE',
'jupyter/base-notebook:latest')
+
+# Hub Configuration
+c.JupyterHub.hub_ip = '0.0.0.0'
+c.JupyterHub.hub_port = 8081
+c.JupyterHub.hub_connect_ip = 'jupyterhub'
+c.JupyterHub.shutdown_on_logout = True
+
+# External URL
+c.JupyterHub.external_url = 'https://hub.dev.cybershuttle.org'
+
+# Logging
+c.JupyterHub.log_level = 'DEBUG'
+
+# Terminate idle notebook containers
+c.JupyterHub.services = [
+ {
+ "name": "jupyterhub-idle-culler-service",
+ "admin": True,
+ "command": [sys.executable, "-m", "jupyterhub_idle_culler",
"--timeout=3600"],
+ }
+]
+
+c.JupyterHub.load_roles = [
+ {
+ "name": "jupyterhub-idle-culler-role",
+ "scopes": [
+ "list:users",
+ "read:users:activity",
+ "read:servers",
+ "delete:servers",
+ ],
+ "services": ["jupyterhub-idle-culler-service"],
+ }
+]
+
+# SSL Termination
+c.JupyterHub.bind_url = 'http://:8000'
+c.JupyterHub.external_ssl = True
+
+# Custom templates - Login
+c.JupyterHub.template_paths = ['/srv/jupyterhub/custom_templates']
+c.OAuthenticator.login_service = "Sign in with Existing Institution
Credentials"
diff --git
a/modules/research-framework/research-hub/compose/user-container/Dockerfile
b/modules/research-framework/research-hub/compose/user-container/Dockerfile
new file mode 100644
index 0000000000..eb24b5d0b1
--- /dev/null
+++ b/modules/research-framework/research-hub/compose/user-container/Dockerfile
@@ -0,0 +1,12 @@
+FROM jupyter/base-notebook:latest
+
+COPY init.sh /usr/local/bin/init.sh
+
+USER root
+RUN chmod +x /usr/local/bin/init.sh
+RUN apt-get update && apt-get install -y git
+
+USER $NB_USER
+
+ENTRYPOINT ["/usr/local/bin/init.sh"]
+CMD ["start-notebook.sh"]
\ No newline at end of file
diff --git
a/modules/research-framework/research-hub/compose/user-container/bootstrap.sh
b/modules/research-framework/research-hub/compose/user-container/bootstrap.sh
new file mode 100644
index 0000000000..b4ae3f6ed5
--- /dev/null
+++
b/modules/research-framework/research-hub/compose/user-container/bootstrap.sh
@@ -0,0 +1 @@
+jupyter lab --config=/jupyter_lab_config.py --ip=0.0.0.0 --port=8888
--no-browser --allow-root --NotebookApp.token=''
\ No newline at end of file
diff --git
a/modules/research-framework/research-hub/compose/user-container/build-container.sh
b/modules/research-framework/research-hub/compose/user-container/build-container.sh
new file mode 100755
index 0000000000..18bc1d1a25
--- /dev/null
+++
b/modules/research-framework/research-hub/compose/user-container/build-container.sh
@@ -0,0 +1,2 @@
+docker build --platform linux/x86_64 -t cybershuttle/dev_jupyterlab-base .
+docker push cybershuttle/dev_jupyterlab-base
\ No newline at end of file
diff --git
a/modules/research-framework/research-hub/compose/user-container/init.sh
b/modules/research-framework/research-hub/compose/user-container/init.sh
new file mode 100755
index 0000000000..4ec7cb2c06
--- /dev/null
+++ b/modules/research-framework/research-hub/compose/user-container/init.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+TARGET_DIR="/home/jovyan/work"
+SHARED_TMP="/home/jovyan/shared_data_tmp"
+
+mkdir -p "$TARGET_DIR"
+
+if [ ! -f "$TARGET_DIR/.initialized" ]; then
+ echo "Copying default Docker files into the mounted workspace..."
+# cp -r /tmp/default_data/. "$TARGET_DIR/"
+ touch "$TARGET_DIR/.initialized"
+ chown -R jovyan:users "$TARGET_DIR"
+
+ # If $GIT_URL is set, clone the repo into the workspace
+ if [ -n "$GIT_URL" ]; then
+ echo "Cloning repo from $GIT_URL..."
+ cd "$TARGET_DIR"
+ git clone "$GIT_URL" repo
+ chown -R jovyan:users "repo"
+ fi
+else
+ echo "Docker default files already exist, skipping copy."
+fi
+
+# If there's a shared_data_tmp directory mounted, symlink them
+if [ -d "$SHARED_TMP" ]; then
+ echo "Creating symlinks for shared read-only data inside $TARGET_DIR..."
+ for item in "$SHARED_TMP"/*; do
+ ln -s "$item" "$TARGET_DIR/$(basename "$item")"
+ done
+fi
+
+exec "$@"