This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch cybershuttle-dev
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/cybershuttle-dev by this push:
new 0099a40652 CyberFaCES docker file
0099a40652 is described below
commit 0099a40652850897a7fd9217adc08b845bffd5a4
Author: Lahiru Jayathilake <[email protected]>
AuthorDate: Tue Feb 25 15:34:42 2025 -0500
CyberFaCES docker file
* initial docker config files for cyberfaces
* docker dependencies
---
.../deployments/cyberfaces/agent/Dockerfile | 25 ++
.../jupyter/deployments/cyberfaces/agent/README.md | 14 +
.../cyberfaces/agent/build-container.sh | 6 +
.../deployments/cyberfaces/agent/docker-agent.sh | 4 +
.../jupyter/deployments/cyberfaces/agent/kernel.py | 145 ++++++++
.../deployments/cyberfaces/jupyterlab/Dockerfile | 14 +
.../cyberfaces/jupyterlab/build-jupyter-image.sh | 1 +
.../cyberfaces/jupyterlab/labconfig/__init__.py | 0
.../jupyterlab/labconfig/airavata_magics.py | 404 +++++++++++++++++++++
.../cyberfaces/jupyterlab/labconfig/bootstrap.sh | 2 +
.../cyberfaces/jupyterlab/labconfig/device_auth.py | 58 +++
.../jupyterlab/labconfig/jupyter_lab_config.py | 25 ++
.../jupyterlab/run-jupyter-lab-container.sh | 1 +
13 files changed, 699 insertions(+)
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/Dockerfile
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/Dockerfile
new file mode 100644
index 0000000000..3521c00a86
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.8.13-slim
+
+USER root
+
+RUN pip install pandas
+RUN pip install geopandas
+RUN pip install pygeos
+RUN pip install matplotlib
+RUN pip install "numpy<2"
+RUN apt update
+RUN apt install -y gdal-bin
+RUN pip install -U libpysal
+RUN pip install -U esda
+RUN pip3 install contextily
+
+RUN pip install flask jupyter jupyter-client wget jupyter_contrib_nbextensions
\
+ utm matplotlib_scalebar earthpy patool cdsapi gdown geojson pyarrow pooch
zarr cython dask fsspec gcsfs \
+ intake intake-esm cmaps cartopy regionmask cdo-api-py progressbar
googledrivedownloader cartopy s3fs pywbt cfgrib \
+ tensorflow seaborn earthengine-api folium scikit-learn netCDF4 xarray
statsmodels pmdarima \
+ pygridmet hydrofunctions pynhd missingno geemap numba==0.56.3 scipy \
+ rasterio ipyleaflet bsddb3 scikit-learn
+RUN mkdir -p /opt/jupyter
+RUN python -m venv /opt/jupyter/venv
+ADD airavata-agent-linux /opt/airavata-agent
+ADD kernel.py /opt/jupyter/kernel.py
\ No newline at end of file
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/README.md
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/README.md
new file mode 100644
index 0000000000..bf95186cc5
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/README.md
@@ -0,0 +1,14 @@
+## Setting up Dev Environment
+
+### Agent
+Build the Agent
+```shell
+./build-container.sh
+```
+
+Execute the following command to run an agent locally.
+```shell
+docker run --rm -v $(pwd):/workspace -w /workspace
cybershuttle/remote-agent-cyberfaces bash -c "./docker-agent.sh"
+```
+
+
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/build-container.sh
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/build-container.sh
new file mode 100755
index 0000000000..d821caca3a
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/build-container.sh
@@ -0,0 +1,6 @@
+cd ../../../../
+env GOOS=linux GOARCH=amd64 go build
+cp airavata-agent jupyter/deployments/cyberfaces/agent/airavata-agent-linux
+cd jupyter/deployments/cyberfaces/agent
+docker build --platform linux/x86_64 -t cybershuttle/remote-agent-cyberfaces .
+docker push cybershuttle/remote-agent-cyberfaces
\ No newline at end of file
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/docker-agent.sh
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/docker-agent.sh
new file mode 100755
index 0000000000..9978af18b6
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/docker-agent.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+/opt/airavata-agent api.gateway.cybershuttle.org:19900 agent2
+
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/kernel.py
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/kernel.py
new file mode 100644
index 0000000000..c9bd281479
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/agent/kernel.py
@@ -0,0 +1,145 @@
+import time
+from jupyter_client import KernelManager
+from flask import Flask, request, jsonify
+import os
+import json
+import re
+
+
+app = Flask(__name__)
+
+km = None
+kc = None
+
+kernel_running = False
+
+ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
+
[email protected]('/start', methods=['GET'])
+def start_kernel():
+
+ global km
+ global kc
+ global kernel_running
+
+ if kernel_running:
+ return "Kernel already running"
+ # Create a new kernel manager
+ km = KernelManager(kernel_name='python3')
+ km.start_kernel()
+
+ # Create a client to interact with the kernel
+ kc = km.client()
+ kc.start_channels()
+
+ # Ensure the client is connected before executing code
+ kc.wait_for_ready()
+ kernel_running = True
+ return "Kernel started"
+
+def strip_ansi_codes(text):
+ return ansi_escape.sub('', text)
+
[email protected]('/execute', methods=['POST'])
+def execute():
+
+ global km
+ global kc
+
+ code = request.json.get('code', '')
+ if not code:
+ return jsonify({'error': 'No code provided'}), 400
+
+ kc.execute(code)
+
+ outputs = []
+ execution_noticed = False
+
+ while True:
+ try:
+ msg = kc.get_iopub_msg(timeout=5)
+
+ content = msg.get("content", {})
+ msg_type = msg.get("msg_type", "")
+
+ # When a message with the text stream comes and it's the result of
our execution
+ if msg_type == "execute_input":
+ execution_noticed = True
+
+ # Handle stdout streams
+ if msg_type == "stream" and content.get("name") == "stdout":
+ outputs.append({
+ "output_type": "stream",
+ "name": "stdout",
+ "text": content.get("text", "")
+ })
+
+ # Handle stderr streams
+ if msg_type == "stream" and content.get("name") == "stderr":
+ outputs.append({
+ "output_type": "stream",
+ "name": "stderr",
+ "text": content.get("text", "")
+ })
+
+ # Handle display data (e.g. plots)
+ if msg_type == "display_data":
+ outputs.append({
+ "output_type": "display_data",
+ "data": content.get("data", {}),
+ "metadata": content.get("metadata", {})
+ })
+
+ # Handle execution results (e.g. return values)
+ if msg_type == "execute_result":
+ outputs.append({
+ "output_type": "execute_result",
+ "data": content.get("data", {}),
+ "metadata": content.get("metadata", {}),
+ "execution_count": content.get("execution_count", None)
+ })
+
+ # Handle errors
+ if msg_type == "error":
+ # Strip ANSI codes from traceback
+ clean_traceback = [strip_ansi_codes(line) for line in
content.get("traceback", [])]
+ outputs.append({
+ "output_type": "error",
+ "ename": content.get("ename", ""),
+ "evalue": content.get("evalue", ""),
+ "traceback": clean_traceback
+ })
+
+ # Check for end of execution
+ if msg_type == "status" and content.get("execution_state") ==
"idle" and execution_noticed:
+ break
+
+ except KeyboardInterrupt:
+ return jsonify({'error': "Execution interrupted by user"}), 500
+ except Exception as e:
+ print(f"Error while getting Jupyter message: {str(e)}")
+
+ response = {
+ "outputs": outputs
+ }
+
+ return jsonify(response), 200
+
[email protected]('/stop', methods=['GET'])
+def stop():
+
+ global km
+ global kc
+ global kernel_running
+
+ if not kernel_running:
+ return "Kernel is not running to shut down"
+
+ kc.stop_channels()
+ km.shutdown_kernel()
+ kernel_running = False
+ return 'Kernel shutting down...'
+
+
+if __name__ == '__main__':
+ app.run(port=15000)
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/Dockerfile
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/Dockerfile
new file mode 100644
index 0000000000..9dc84c72c7
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/Dockerfile
@@ -0,0 +1,14 @@
+FROM cybershuttle/base-cyberfaces
+
+USER root
+
+COPY labconfig/jupyter_lab_config.py /jupyter_lab_config.py
+COPY labconfig/airavata_magics.py /airavata_magics.py
+COPY labconfig/__init__.py /__init__.py
+COPY labconfig/device_auth.py /device_auth.py
+COPY labconfig/bootstrap.sh /bootstrap.sh
+RUN chmod +x /bootstrap.sh
+
+USER ${NB_UID}
+
+CMD ["start-notebook.sh","--NotebookApp.iopub_data_rate_limit=1e10"]
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/build-jupyter-image.sh
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/build-jupyter-image.sh
new file mode 100755
index 0000000000..234c95876a
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/build-jupyter-image.sh
@@ -0,0 +1 @@
+docker build --platform linux/x86_64 -t cybershuttle/cyberfaces-jupyter-lab .
\ No newline at end of file
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/__init__.py
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/airavata_magics.py
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/airavata_magics.py
new file mode 100644
index 0000000000..b6fd96db2a
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/airavata_magics.py
@@ -0,0 +1,404 @@
+import base64
+import binascii
+import json
+import time
+from pathlib import Path
+from typing import NamedTuple
+import jwt
+import os
+import requests
+from IPython.core.magic import register_cell_magic, register_line_magic
+from IPython.display import HTML, Image, display
+from device_auth import DeviceFlowAuthenticator
+
+
+AgentInfo = NamedTuple('AgentInfo', [
+ ('agentId', str),
+ ('experimentId', str),
+ ('processId', str),
+ ('cluster', str),
+ ('queue', str),
+ ('cpus', int),
+ ('memory', int),
+ ('walltime', int),
+ ('gateway_id', str),
+ ('group', str),
+])
+api_base_url = "https://api.gateway.cybershuttle.org"
+file_server_url = "http://3.142.234.94:8050"
+current_agent : AgentInfo | None = None
+MSG_NOT_INITIALIZED = r"Remote agent not initialized. Please run %init_remote
cluster=<cluster> cpu=<cpu> memory=<memory mb> queue=<queue> walltime=<walltime
minutes> group=<group>"
+
+
+def get_access_token() -> str | None:
+ token_from_env = os.getenv('CS_ACCESS_TOKEN')
+ if token_from_env:
+ return token_from_env
+ EXPLICIT_TOKEN_FILE = Path("~").expanduser() / "csagent" / "token" /
"keys.json"
+ if EXPLICIT_TOKEN_FILE.exists():
+ with open(EXPLICIT_TOKEN_FILE, "r") as f:
+ return json.load(f).get("access_token")
+
+
+def get_agent_status() -> dict | None:
+ if not current_agent:
+ return print(MSG_NOT_INITIALIZED)
+ url = f"{api_base_url}/api/v1/agent/{current_agent.agentId}"
+ response = requests.get(url)
+ if response.status_code == 202:
+ return response.json()
+ return print(f"Got [{response.status_code}] Response: {response.text}")
+
+
+def get_process_id(experiment_id: str, headers) -> str:
+ """
+ Get process id by experiment id
+
+ """
+ url = f"{api_base_url}/api/v1/exp/{experiment_id}/process"
+ process_id = ""
+ while not process_id:
+ response = requests.get(url, headers=headers)
+ if response.status_code == 200:
+ process_id = response.json().get("processId")
+ else:
+ time.sleep(5)
+ return process_id
+
+
+def submit_agent_job(experiment_name, cluster, queue, cpus, memory, walltime,
access_token, group, gateway_id='default'):
+ global current_agent
+
+ # URL to which the POST request will be sent
+ url = api_base_url + '/api/v1/exp/launch'
+
+ # Data to be sent in the POST request
+ data = {
+ 'experimentName': experiment_name,
+ 'remoteCluster': cluster,
+ 'cpuCount': cpus,
+ 'nodeCount': 1,
+ 'memory': memory,
+ 'wallTime': walltime,
+ 'queue': queue,
+ 'group': group,
+ }
+
+ # Convert the data to JSON format
+ json_data = json.dumps(data)
+
+ decode = jwt.decode(access_token, options={"verify_signature": False})
+ user_id = decode['preferred_username']
+ claimsMap = {
+ "userName": user_id,
+ "gatewayID": gateway_id
+ }
+
+ # Headers
+ headers = {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + access_token,
+ 'X-Claims': json.dumps(claimsMap)
+ }
+
+ # Send the POST request
+ response = requests.post(url, headers=headers, data=json_data)
+
+ # Check if the request was successful
+ if response.status_code == 200:
+ # Parse the JSON response
+ obj = response.json()
+ current_agent = AgentInfo(
+ agentId=obj['agentId'],
+ experimentId=obj['experimentId'],
+ processId=get_process_id(obj['experimentId'], headers=headers),
+ cluster=cluster,
+ queue=queue,
+ cpus=cpus,
+ memory=memory,
+ walltime=walltime,
+ gateway_id=gateway_id,
+ group=group,
+ )
+ print('Agent Initialized:', current_agent)
+ else:
+ print('Failed to send POST request. Status code:',
response.status_code)
+ print('Response:', response.text)
+
+
+def terminate_agent(access_token, gateway_id='default'):
+ global current_agent
+ if not current_agent:
+ return print(MSG_NOT_INITIALIZED)
+
+ expId = current_agent.experimentId
+ url = api_base_url + '/api/v1/exp/terminate/' + expId
+
+ decode = jwt.decode(access_token, options={"verify_signature": False})
+ user_id = decode['preferred_username']
+ claimsMap = {
+ "userName": user_id,
+ "gatewayID": gateway_id
+ }
+
+ # Headers
+ headers = {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + access_token,
+ 'X-Claims': json.dumps(claimsMap)
+ }
+
+ # Send the POST request
+ response = requests.get(url, headers=headers)
+
+ if response.status_code == 200:
+ # Parse the JSON response
+ response_data = response.json()
+ print('Agent terminated:', response_data)
+ current_agent = None
+ else:
+ print('Failed to send termination request. Status code:',
response.status_code)
+ print('Response:', response.text)
+
+
+@register_cell_magic
+def run_remote(line, cell):
+ global current_agent
+ if not current_agent:
+ return print(MSG_NOT_INITIALIZED)
+
+ url = api_base_url + '/api/v1/agent/executejupyterrequest'
+
+ data = {
+ "sessionId": "session1",
+ "keepAlive": True,
+ "code": cell,
+ "agentId": current_agent.agentId
+ }
+
+ json_data = json.dumps(data)
+ response = requests.post(url, headers={'Content-Type':
'application/json'}, data=json_data)
+ execution_resp = response.json()
+ execution_id = execution_resp.get("executionId")
+ error = execution_resp.get("error")
+ if error:
+ print("Cell execution failed. Error: " + error)
+ if execution_id:
+ while True:
+ url = api_base_url + "/api/v1/agent/executejupyterresponse/" +
execution_id
+ response = requests.get(url, headers={'Accept':
'application/json'})
+ json_response = response.json()
+ if json_response.get('available'):
+ result_str = json_response.get('responseString')
+ try:
+ result = json.loads(result_str)
+ except json.JSONDecodeError as e:
+ print(f"Failed to decode JSON response: {e}")
+ break
+
+ if 'outputs' in result:
+ for output in result['outputs']:
+ output_type = output.get('output_type')
+ if output_type == 'display_data':
+ data_obj = output.get('data', {})
+ if 'image/png' in data_obj:
+ image_data = data_obj['image/png']
+ try:
+ image_bytes = base64.b64decode(image_data)
+ display(Image(data=image_bytes,
format='png'))
+ except binascii.Error as e:
+ print(f"Failed to decode image data: {e}")
+ # Ignoring any texts in the display data
+ # if 'text/plain' in data_obj:
+ # print(data_obj['text/plain'])
+
+ elif output_type == 'stream':
+ stream_name = output.get('name', 'stdout')
+ stream_text = output.get('text', '')
+ if stream_name == 'stderr':
+ error_html = f"""
+ <div style="
+ color: #a71d5d;
+ background-color: #fdd;
+ border: 1px solid #a71d5d;
+ padding: 5px;
+ border-radius: 5px;
+ font-family: Consolas, 'Courier New',
monospace;
+ white-space: pre-wrap;
+ ">
+ {stream_text}
+ </div>
+ """
+ display(HTML(error_html))
+ else:
+ print(stream_text)
+
+ elif output_type == 'error':
+ ename = output.get('ename', 'Error')
+ evalue = output.get('evalue', '')
+ traceback = output.get('traceback', [])
+
+ error_html = f"""
+ <div style="
+ color: #a71d5d;
+ background-color: #fdd;
+ border: 1px solid #a71d5d;
+ padding: 5px;
+ border-radius: 5px;
+ font-family: Consolas, 'Courier New',
monospace;
+ ">
+ <pre><strong>{ename}: {evalue}</strong>
+ """
+ for line in traceback:
+ error_html += f"{line}\n"
+ error_html += "</pre></div>"
+ display(HTML(error_html))
+
+ elif output_type == 'execute_result':
+ data_obj = output.get('data', {})
+ if 'text/plain' in data_obj:
+ print(data_obj['text/plain'])
+ else:
+ if 'result' in result:
+ print(result['result'])
+ elif 'error' in result:
+ print(result['error']['ename'])
+ print(result['error']['evalue'])
+ print(result['error']['traceback'])
+ elif 'display' in result:
+ data_obj = result['display'].get('data', {})
+ if 'image/png' in data_obj:
+ image_data = data_obj['image/png']
+ try:
+ image_bytes = base64.b64decode(image_data)
+ display(Image(data=image_bytes, format='png'))
+ except binascii.Error as e:
+ print(f"Failed to decode image data: {e}")
+ break
+ time.sleep(1)
+
+
+@register_line_magic
+def cs_login(line):
+ try:
+ authenticator = DeviceFlowAuthenticator()
+ authenticator.login()
+ except ValueError as e:
+ print(f"Configuration error: {e}")
+
+
+@register_line_magic
+def init_remote(line):
+ if current_agent:
+ status = get_agent_status()
+ if status:
+ if status['agentUp']:
+ print("An agent is already running. Please terminate it first
by running %terminate_remote")
+ return
+ else:
+ print("An agent was scheduled. Please terminate it first by
running %terminate_remote")
+ return
+
+ access_token = get_access_token()
+ pairs = line.split()
+
+ # Initialize variable to store the cluster value
+ cluster_value = None
+ memory_value = None
+ cpu_value = None
+ queue_value = None
+ walltime_value = None
+ group_value = ""
+
+ # Iterate through the pairs to find the cluster value
+ for pair in pairs:
+ if pair.startswith("cluster="):
+ cluster_value = pair.split("=")[1]
+ if pair.startswith("cpu="):
+ cpu_value = pair.split("=")[1]
+ if pair.startswith("memory="):
+ memory_value = pair.split("=")[1]
+ if pair.startswith("queue="):
+ queue_value = pair.split("=")[1]
+ if pair.startswith("walltime="):
+ walltime_value = pair.split("=")[1]
+ if pair.startswith("group="):
+ group_value = pair.split("=")[1]
+
+ submit_agent_job('CS_Agent', cluster_value, queue_value, cpu_value,
memory_value, walltime_value, access_token, group_value)
+
+
+@register_line_magic
+def status_remote(line):
+ status = get_agent_status()
+ if status:
+ if status['agentUp']:
+ print("Agent", status['agentId'], 'is running')
+ else:
+ print("Agent", status['agentId'], 'is still preparing. Please
wait')
+
+
+@register_line_magic
+def terminate_remote(line):
+ global current_agent
+ access_token = get_access_token()
+ if current_agent:
+ terminate_agent(access_token)
+
+
+@register_line_magic
+def push_remote(line):
+ if not current_agent:
+ return print(MSG_NOT_INITIALIZED)
+ pairs = line.split()
+ remot_path = None
+ local_path = None
+ for pair in pairs:
+ if pair.startswith("source="):
+ local_path = pair.split("=")[1]
+ if pair.startswith("target="):
+ remot_path = pair.split("=")[1]
+ # validate paths
+ if not remot_path or not local_path:
+ return print("Please provide paths for both source and target")
+ # upload file
+ print(f"Pushing local:{local_path} to remote:{remot_path}")
+ url =
f"{file_server_url}/upload/live/{current_agent.processId}/{remot_path}"
+ with open(local_path, "rb") as file:
+ files = {"file": file}
+ response = requests.post(url, files=files)
+ print(f"[{response.status_code}] Uploaded local:{local_path} to
remote:{remot_path}")
+
+
+@register_line_magic
+def pull_remote(line):
+ if not current_agent:
+ return print(MSG_NOT_INITIALIZED)
+ pairs = line.split()
+ remot_path = None
+ local_path = None
+ for pair in pairs:
+ if pair.startswith("source="):
+ remot_path = pair.split("=")[1]
+ if pair.startswith("target="):
+ local_path = pair.split("=")[1]
+ # validate paths
+ if not remot_path or not local_path:
+ return print("Please provide paths for both source and target")
+ # download file
+ print(f"Pulling remote:{remot_path} to local:{local_path}")
+ url =
f"{file_server_url}/download/live/{current_agent.processId}/{remot_path}"
+ response = requests.get(url)
+ with open(local_path, "wb") as file:
+ file.write(response.content)
+ print(f"[{response.status_code}] Downloaded remote:{remot_path} to
local:{local_path}")
+
+
+def load_ipython_extension(ipython):
+ ipython.register_magic_function(cs_login)
+ ipython.register_magic_function(init_remote)
+ ipython.register_magic_function(status_remote)
+ ipython.register_magic_function(terminate_remote)
+ ipython.register_magic_function(run_remote)
+ ipython.register_magic_function(push_remote)
+ ipython.register_magic_function(pull_remote)
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/bootstrap.sh
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/bootstrap.sh
new file mode 100644
index 0000000000..754a6e8f52
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/bootstrap.sh
@@ -0,0 +1,2 @@
+cd /home/jupyter-notebook-examples && git pull && cd -
+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/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/device_auth.py
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/device_auth.py
new file mode 100644
index 0000000000..269085ca69
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/device_auth.py
@@ -0,0 +1,58 @@
+import requests
+import time
+import os
+# Load environment variables from .env file
+
+class DeviceFlowAuthenticator:
+ def __init__(self):
+ self.client_id = "cybershuttle-agent"
+ self.realm = "default"
+ self.auth_server_url = "https://auth.cybershuttle.org"
+
+ if not self.client_id or not self.realm or not self.auth_server_url:
+ raise ValueError("Missing required environment variables for
client ID, realm, or auth server URL")
+
+ self.device_code = None
+ self.interval = None
+
+ def login(self):
+ # Step 1: Request device and user code
+ auth_device_url =
f"{self.auth_server_url}/realms/{self.realm}/protocol/openid-connect/auth/device"
+ response = requests.post(auth_device_url, data={"client_id":
self.client_id, "scope": "openid"})
+
+ if response.status_code != 200:
+ print(f"Error in device authorization request:
{response.status_code} - {response.text}")
+ return
+
+ data = response.json()
+ self.device_code = data.get("device_code")
+ self.interval = data.get("interval", 5)
+
+ print(f"User code: {data.get('user_code')}")
+ print(f"Please authenticate by visiting:
{data.get('verification_uri_complete')}")
+
+ # Step 2: Poll for the token
+ self.poll_for_token()
+
+ def poll_for_token(self):
+ token_url =
f"{self.auth_server_url}/realms/{self.realm}/protocol/openid-connect/token"
+ while True:
+ response = requests.post(token_url, data={
+ "client_id": self.client_id,
+ "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
+ "device_code": self.device_code
+ })
+
+ if response.status_code == 200:
+ data = response.json()
+ access_token = data.get("access_token")
+ print(f"Received access token")
+ os.environ['CS_ACCESS_TOKEN'] = access_token
+ break
+ elif response.status_code == 400 and response.json().get("error")
== "authorization_pending":
+ print("Authorization pending, retrying...")
+ else:
+ print(f"Error in token request: {response.status_code} -
{response.text}")
+ break
+
+ time.sleep(self.interval)
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/jupyter_lab_config.py
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/jupyter_lab_config.py
new file mode 100644
index 0000000000..a160cd77cb
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/labconfig/jupyter_lab_config.py
@@ -0,0 +1,25 @@
+import sys
+sys.path.append('/')
+
+c = get_config()
+
+c.InteractiveShellApp.exec_lines = [
+ "import sys"
+ "sys.path.append('/')"
+ "import airavata_magics",
+ "airavata_magics.load_ipython_extension(get_ipython())"
+]
+
+# Set the IP address Jupyter Lab will listen on
+c.ServerApp.ip = '0.0.0.0'
+
+# Set the port Jupyter Lab will listen on
+c.ServerApp.port = 8888
+
+# Don't open the browser by default
+c.ServerApp.open_browser = False
+
+c.FileContentsManager.use_atomic_writing = False
+
+# Allow root access
+c.ServerApp.allow_root = True
\ No newline at end of file
diff --git
a/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/run-jupyter-lab-container.sh
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/run-jupyter-lab-container.sh
new file mode 100755
index 0000000000..81c92419c2
--- /dev/null
+++
b/modules/agent-framework/airavata-agent/jupyter/deployments/cyberfaces/jupyterlab/run-jupyter-lab-container.sh
@@ -0,0 +1 @@
+docker run -p 18888:8888 -it cybershuttle/cyberfaces-jupyter-lab
\ No newline at end of file