commit 7c1c161cedd3b2569f4d30041e6bb7ac41c6245e Author: DerGrumpf Date: Tue Mar 24 10:59:50 2026 +0100 Init diff --git a/.kubeconfig b/.kubeconfig new file mode 100644 index 0000000..d57bb6f --- /dev/null +++ b/.kubeconfig @@ -0,0 +1,18 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJY1R6R0hSU3FrblF3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpBek1qUXdNREExTXpOYUZ3MHpOakF6TWpFd01ERXdNek5hTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUN2aVFqN0Z0dVhpZlZRYURtNUNNYmxNYXFaMXJXVGZDMk5SSEJWcVN4M0YxS1IzWlpRZ1J3YjZ5eSsKbU5FLzRZV2ZOK1NJUVEvbG93Y2llMXZxLzl6QTFNaXY4enM0YlRQaU5sY0hRTm9iYkRiUHBUNkNVdXpvdjdqRgpqbXhiQ3BlNlhpSmlTVHQvT3h6Wlg3NzZiUVAvNkxUTEdtdm1xaWxTa0I4Rjl6eVlFRmJrbkVYQkp1UHBXY0pUCkdCUmpyMzJzS05RbE13c2thSFhnNmVsRzhVa1FFMDNQeUpITi8yazlNZWpVWmVwQll2VE1mKzZCMW56ZDdVWWoKKzA2Z3EzWUhlUDE3R0kxVjRzR0UwMDJ3a1RsS213UUNiV1p2NkZMRHl3M01hL0RWdGFjQ0pQMklVeFhHeCs3agowRE0wZXZMN2ZMUnNGbGtSNGl2U09VcmJIamlQQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJSNjE3TXZ0L01uQXR0aHZXZHZoTWxMcEs3MDZ6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQWczVmNNUVFYcQpxSXNhd1RxOXByM05oY282Zk83U3g0MkNncXZXZzFIMXYxR1hiNVMzaDlaaUN1cVF4MkUvT0lNSmdUdytNblY5ClNZMVJKZ1Qvc0RGQk5jQ3ZLM1ZPVmpCdmFmRUh3b3V4dTlGSzdTaityaWJ4STNod3dsNFVrSit6Ly93ZWl0L0IKMXRWSXE5Y1lLblhFck9sQmJ1KzZYR3phTmRzOVBPSGZwU2g3ZWlhTFVZemNDaXN3b2NQN0NVdE53bUViekVZNwpLRFZKclJSWEtTNTdtYnNXSlpnWVFEZGlDVzMvNkI4TUg1YTVoakdjempadjBNc2hsVmtuWWlNL3RrbEExdWxCCi9TM2ZXdnhuallicWlWcXJDbzRMSFhwV0tHbFdyY1ZtSFJVcnZ1ZjlYQ09ncEhyUnRVVHFOOFVUQy95MXN0eDAKZjAzcHd0aGZXSU8rCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://127.0.0.1:39209 + name: kind-kupyter +contexts: +- context: + cluster: kind-kupyter + user: kind-kupyter + name: kind-kupyter +current-context: kind-kupyter +kind: Config +users: +- name: kind-kupyter + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJT0xIdlBuY3E1VWN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpBek1qUXdNREExTXpOYUZ3MHlOekF6TWpRd01ERXdNek5hTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDdFRVMzgKYXFqTzVlbG5hclhRUWxDcEZiTDNHSnpVY0lhY2ROeHV5NzFZUitEZmc2bEdJK3puQlFVTlpYOXhpak5TbHlZNgpiQ00rQk9IR09vVGxsdUxhL1RXbWNaUnhYYVBCeVpHY3ZkWVJCNFNVT2JlZ1BwT0xjajFBaXdvbU9IQUVOYXNzClZUeFlEcXlOUnd5M1cwNGFVcWNMREMyVUtDU0hEUVk1dlphOWo4MXpkdjl0K0g5L2RZb1diM3RHOTVySlRndE0KUGhhcDZkNlpoVUVXSDl1aE5Ha2YreE01Zi9KaUVRNnovWmc1alBsdHVHcGhaSUR5QmhUcUJaZnNJTjN3UllreQpiRnp6SndMZTZVVmhRQlE2YjVjZjlHYXVndWVocmFLb0wxUThJVFJuR2JvR0ZEb3dUbkQwaGN1V0RqZi9QbnV2CnZIUWk4L0pqK3pKcDBWek5BZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkhyWHN5KzM4eWNDMjJHOQpaMitFeVV1a3J2VHJNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUNXUHhIdDJpM0ZERHkyd3JOeDBJZkFLYUF3CldWMzVCNTcxblVNTWltNVFjalVMUEU1c0praEpra3VGNGRZTlU5amM4K0c1WHF6QnpyQlpBajg5dWI3UmM2MXAKbVpQekEvWHlUbjZBK1BBRFBBRlkxSE9FMmdtSmd4eFZjV08rRmtyRzBZN1pjemRQdzdJRjQySzFDYzVSMDU1TApENjNGMkhaWEQ5d3NYWC8zYS9ReWpuMlFFN0dXOWc4MTUya1lMeVYycGN3cVo5aTFRNlgyUjBzbXRiOEt4UFZDCkdhS2ZwKy9NVXdZOE40WHdOakIraVo5anVvWU1mR095TFBieGIzTThMblR3bkRxNHdRRjV5c24vVFJQWCtFNHAKbnFCblNXbDVSVzdERi9VVi9na0pIbDlxWTJydFlEZkkyWHJ5YzJKdVhUSTh3N1BjRi80QVlFQnQvQkZXCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBclUxTi9HcW96dVhwWjJxMTBFSlFxUld5OXhpYzFIQ0duSFRjYnN1OVdFZmczNE9wClJpUHM1d1VGRFdWL2NZb3pVcGNtT213alBnVGh4anFFNVpiaTJ2MDFwbkdVY1YyandjbVJuTDNXRVFlRWxEbTMKb0Q2VGkzSTlRSXNLSmpod0JEV3JMRlU4V0E2c2pVY010MXRPR2xLbkN3d3RsQ2draHcwR09iMld2WS9OYzNiLwpiZmgvZjNXS0ZtOTdSdmVheVU0TFRENFdxZW5lbVlWQkZoL2JvVFJwSC9zVE9YL3lZaEVPcy8yWU9ZejViYmhxCllXU0E4Z1lVNmdXWDdDRGQ4RVdKTW14Yzh5Y0MzdWxGWVVBVU9tK1hIL1Jtcm9Mbm9hMmlxQzlVUENFMFp4bTYKQmhRNk1FNXc5SVhMbGc0My96NTdyN3gwSXZQeVkvc3lhZEZjelFJREFRQUJBb0lCQUFLSjMxcVROV1hTZUZqTApkMTVWbWxqZnVIOW1IT1gvdi9rS3ZTL2lUQ08rNmN4Y1lWNWxxRks2QUJqeUk2dkdHbnBiUEhRZW9XV0hMTWQ2CmsvZkkvZ20zSzlJRVYraFJOdFRmM3dJc3hiWDZKamNGb1dyM2Y3SExPcHAzYnU2Z1pRT3F4WmNncUlHaHRXVmMKWlJOS2d4cGZtNUxOMnQwUXVYaEErSlpmOEpWV1A3OHFsaUNFQ2ZEd3pCRVpRVHVoYmhwRGhvNjBxL0UzU3RQVQprTGMzVitONnFEK0szeTZpM0hWU3lldkpYWWg0RXJCNlY1RTVweU5wZU90d1BhbThVRG91ZWZpcFZJQkVWVHhvCm00NVNCanZSQnRRYWRxMGN5M0p0L3N3aEtJQUVpT0c2WE1icmc4bzFWSDRNNnJiWmlQdDBiODVOUVJNd0FuR2kKMUsrTU9RRUNnWUVBeWtWMFZlSmdObS8rSVVsblRscGgzOENKd3kydU5QVXBRSHBoQ3pjVUxkQXV0Ri8yNTgvTwpJeVBoMVB5bzNCOXBLVVdmYnRpVjJoZjhXUjV3UjdJakpLWDhzaHRqcEs3TkJvTDhrdnBRUVl0QS9CVS9KR2E0ClRKaWpsbGJNOWtXYzF2dEs5YStQV255U3hCb1psb0hYaE1lRzFMK2JpRE5FcFk4c2hTbzVkTU1DZ1lFQTIxWHIKNnRuelhSRGVtMzdRRmttOXpUL01tSmROQ3MxOVovU2kvNGVRTHpKNkM2RlJUMGNFTHpHVjA3SXdIYjhMZ1JPTwpUY2g3V3RXYngwa2o4L2RVcXNJWXpLYzRhT2hsVVg2aUNpZVZBakdETzg2aFVWT3FkTmVXaFZudDdyZE95cTgxCmtNdkdTTmV6ZlFTMnE0cU1GQVUwcGpVdDQwaiszWFdJdVdvRWp5OENnWUVBbmtzc2QrbnBFYkVqV0RseHQwZlUKUUo4Vk1NR1hDNnF3MWR6d0JTN2RnOXpnTUJqSnlUQS9TaERTc3pQbmtoeWkxOEc4dTZxVDIxSGFFb1JYcWtRbQpiSS9aNmlpMUdqUVNEMzZDMnlNNW01RzNFWkF2RWZXeFZZQSt4WEM0aGlLRVUxbmxsOUFFaC9QbGg4SkZOQnY0CjVkaWdFKzYvY1I5dUlZS2lmTFJHc3JFQ2dZQmZ3cXFtdFpPSURXWnpVekY4bWFOeGFpcGtjS0psVmdRcmorWmUKVkF5Q1hySmtRNEVoY0tzR0E4c2JTdyt3M1FranlLcjNrTkV5ZmxKdDlxUG96eEk3SDFUK2ZQK201ZGZlZGNBLwpXTHE0NDI4ZGZJQjM1bVJrY1ArNXB1SzN0M2FDRFc4QWtjYzNaRjFyOXRQZUh6WTdRMjZTSm1PcmVPSTFSQ3gyCmJ6QWdad0tCZ1FDNUlhVTdzWmlrVVlwbmZJYUZKK1NGTTN4b1lIWVpzNHYvN0t5OEhCM2VqVHlZbXl1QjBZNFYKUDhqZlg4SUxsN0o3a3dKNGpnd2kyWGZxckorOW9SRzZ0N0I5R3EyM3pUR29rSnJXSFRKeXJOcnpUalRRT1A2LwpTNERObWgrM1Mxd1MxTC9IY25VZk1uUmFnMEtraTFIQ0ExdFQzK3Q1a254dWxxYldOUlUyVUE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/Dockerfile.hub b/Dockerfile.hub new file mode 100644 index 0000000..87f0210 --- /dev/null +++ b/Dockerfile.hub @@ -0,0 +1,10 @@ +FROM quay.io/jupyterhub/k8s-hub:4.3.2 + +USER root +RUN apt-get update && apt-get install -y nodejs npm && apt-get clean + +USER jovyan +RUN pip install --upgrade pip +RUN pip install git+https://github.com/jupyter/nbgrader.git@main ngshare_exchange + +COPY ./kupyter-notebook/nbgrader_config.py /etc/jupyter/nbgrader_config.py diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..172d7d3 --- /dev/null +++ b/config.yaml @@ -0,0 +1,89 @@ +hub: + image: + name: localhost/kupyter-hub + tag: latest + pullPolicy: Never + + services: + ngshare: + url: http://ngshare.jupyterhub.svc.cluster.local:8080 + apiToken: 822235aaaf83f0232c799fe948cbb1594b01d7d7b11af051871cc2d3fcb08fe8 + ngshare-admin: + apiToken: setuptoken123456789012345678901234567890123456789012345678901234 + + + db: + type: postgres + url: postgresql+psycopg2://jupyterhub:jupyterhub@jupyterhub-db-postgresql.jupyterhub.svc:5432/jupyterhub + + config: + JupyterHub: + authenticator_class: nativeauthenticator.NativeAuthenticator + admin_users: + - admin + - instructor-Prog + - instructor-Sig + + NativeAuthenticator: + open_signup: false + allowed_users: + - admin + - instructor-Prog + - instructor-Sig + + extraVolumes: + - name: hub-logos + configMap: + name: hub-logos + + extraVolumeMounts: + - name: hub-logos + mountPath: /usr/local/share/jupyterhub/static/images/jupyterhub-80.png + subPath: jupyterhub-80.png + - name: hub-logos + mountPath: /usr/local/share/jupyterhub/static/images/jupyterhub.svg + subPath: jupyterhub.svg + + extraConfig: + ngshare.py: | + c.JupyterHub.services.append({ + 'name': 'ngshare', + 'url': 'http://ngshare.jupyterhub.svc.cluster.local:8080', + 'api_token': '822235aaaf83f0232c799fe948cbb1594b01d7d7b11af051871cc2d3fcb08fe8', + 'oauth_no_confirm': True}) + nbgrader: | + c.JupyterHub.load_groups = { + "formgrader-users": ["admin"] + } + +proxy: + secretToken: "8031b6dca309ff7259a10d1d38c70c4852d17d5bab02c31cbe7d76a3fb60cb66" + +prePuller: + hook: + enabled: false + continuous: + enabled: false + +cull: + enabled: true + timeout: 1800 + every: 300 + +singleuser: + defaultUrl: "/tree" + image: + name: localhost/kupyter-notebook + tag: latest + pullPolicy: Never + memory: + limit: 1G + guarantee: 256M + cpu: + limit: 1 + guarantee: 0.1 + cloudMetadata: + blockWithIptables: false + networkPolicy: + enabled: false + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..8b5984e --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1773734432, + "narHash": "sha256-IF5ppUWh6gHGHYDbtVUyhwy/i7D261P7fWD1bPefOsw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "cda48547b432e8d3b18b4180ba07473762ec8558", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..700bf1b --- /dev/null +++ b/flake.nix @@ -0,0 +1,318 @@ +{ + description = "kupyter – rootless Kubernetes dev shell"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; }; + + get-all = pkgs.writeShellApplication { + name = "get-all"; + runtimeInputs = [ pkgs.kubectl ]; + text = ''kubectl get pods -A''; + }; + + cluster-start = pkgs.writeShellApplication { + name = "cluster-start"; + runtimeInputs = [ + pkgs.kind + pkgs.kubectl + ]; + text = '' + kind create cluster --name kupyter --wait 60s + kubectl config use-context kind-kupyter + echo "✓ Cluster ready" + kubectl cluster-info + ''; + }; + + cluster-stop = pkgs.writeShellApplication { + name = "cluster-stop"; + runtimeInputs = [ pkgs.kind ]; + text = '' + kind delete cluster --name kupyter + rm -f .kubeconfig + echo "✓ Cluster deleted, kubeconfig cleared" + ''; + }; + + tls-init = pkgs.writeShellApplication { + name = "tls-init"; + runtimeInputs = [ + pkgs.openssl + pkgs.kubectl + ]; + text = '' + openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout tls.key \ + -out tls.crt \ + -subj "/CN=jupyterhub.local/O=kupyter" + + kubectl create namespace jupyterhub --dry-run=client -o yaml | kubectl apply -f - + + kubectl create secret tls jupyterhub-tls \ + --cert=tls.crt \ + --key=tls.key \ + --namespace jupyterhub + echo "✓ TLS secret stored in jupyterhub namespace" + ''; + }; + + helm-repos = pkgs.writeShellApplication { + name = "helm-repos"; + runtimeInputs = [ pkgs.kubernetes-helm ]; + text = '' + helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx + helm repo add jupyterhub https://hub.jupyter.org/helm-chart/ + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo add ngshare https://libretexts.github.io/ngshare-helm-repo/ + helm repo add prometheus https://prometheus-community.github.io/helm-charts + helm repo update + echo "✓ all helm repos added and updated" + ''; + }; + + ingress-install = pkgs.writeShellApplication { + name = "ingress-install"; + runtimeInputs = [ + pkgs.kubernetes-helm + pkgs.kubectl + ]; + text = '' + helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \ + --namespace ingress-nginx \ + --create-namespace \ + --set controller.service.type=NodePort \ + --set controller.service.nodePorts.http=30080 \ + --set controller.service.nodePorts.https=30443 + echo "✓ ingress-nginx installed" + kubectl get pods -n ingress-nginx + ''; + }; + + jupyterhub-install = pkgs.writeShellApplication { + name = "jupyterhub-install"; + runtimeInputs = [ + pkgs.kubernetes-helm + pkgs.kubectl + ]; + text = '' + kubectl create namespace jupyterhub --dry-run=client -o yaml | kubectl apply -f - + + kubectl create configmap hub-logos \ + --from-file=jupyterhub-80.png=./kupyter-notebook/logo.png \ + --from-file=jupyterhub.svg=./kupyter-notebook/logo.svg \ + --namespace jupyterhub \ + --dry-run=client -o yaml | kubectl apply -f - + + helm upgrade --install jupyterhub jupyterhub/jupyterhub \ + --namespace jupyterhub \ + --values config.yaml + + kubectl apply -f jupyterhub-ingress.yaml + echo "✓ jupyterhub installing" + kubectl get pods -n jupyterhub + ''; + }; + + db-install = pkgs.writeShellApplication { + name = "db-install"; + runtimeInputs = [ + pkgs.kubernetes-helm + pkgs.kubectl + ]; + text = '' + helm upgrade --install jupyterhub-db bitnami/postgresql \ + --namespace jupyterhub \ + --set auth.username=jupyterhub \ + --set auth.password=jupyterhub \ + --set auth.database=jupyterhub + echo "✓ postgres installing" + kubectl get pods -n jupyterhub + ''; + }; + + notebook-build = pkgs.writeShellApplication { + name = "notebook-build"; + runtimeInputs = [ + pkgs.podman + pkgs.kind + ]; + text = '' + podman build -t kupyter-notebook:latest ./kupyter-notebook + podman save kupyter-notebook:latest -o /tmp/kupyter-notebook.tar + kind load image-archive /tmp/kupyter-notebook.tar --name kupyter + rm /tmp/kupyter-notebook.tar + echo "✓ image built and loaded into cluster" + ''; + }; + + hub-build = pkgs.writeShellApplication { + name = "hub-build"; + runtimeInputs = [ + pkgs.podman + pkgs.kind + ]; + text = '' + podman build -t kupyter-hub:latest -f Dockerfile.hub . + podman save kupyter-hub:latest -o /tmp/kupyter-hub.tar + kind load image-archive /tmp/kupyter-hub.tar --name kupyter + rm /tmp/kupyter-hub.tar + echo "✓ hub image built and loaded" + ''; + }; + + ngshare-install = pkgs.writeShellApplication { + name = "ngshare-install"; + runtimeInputs = [ + pkgs.kubernetes-helm + pkgs.kubectl + ]; + text = '' + helm upgrade --install ngshare ngshare/ngshare \ + --namespace jupyterhub \ + --set ngshare.token=822235aaaf83f0232c799fe948cbb1594b01d7d7b11af051871cc2d3fcb08fe8 \ + --set ngshare.hub_api_token=822235aaaf83f0232c799fe948cbb1594b01d7d7b11af051871cc2d3fcb08fe8 \ + --set ngshare.admins="admin,ngshare-setup" \ + --values ngshare-values.yaml + + echo "Waiting for ngshare..." + kubectl wait pod \ + --for=condition=ready \ + --selector=app.kubernetes.io/name=ngshare \ + --namespace jupyterhub \ + --timeout=120s + + echo "✓ ngshare installed" + kubectl get pods -n jupyterhub + ''; + }; + + ngshare-courses = pkgs.writeShellApplication { + name = "ngshare-courses"; + runtimeInputs = [ pkgs.kubectl ]; + text = '' + HUB_POD=$(kubectl get pod -n jupyterhub -l component=hub -o name) + + kubectl exec -n jupyterhub "$HUB_POD" -- curl -s -X POST \ + "http://ngshare.jupyterhub.svc.cluster.local:8080/services/ngshare/course/Programmieren" \ + -d "instructors=[\"instructor-Prog\"]" \ + -H "Authorization: token setuptoken123456789012345678901234567890123456789012345678901234" + + kubectl exec -n jupyterhub "$HUB_POD" -- curl -s -X POST \ + "http://ngshare.jupyterhub.svc.cluster.local:8080/services/ngshare/course/Signale" \ + -d "instructors=[\"instructor-Sig\"]" \ + -H "Authorization: token setuptoken123456789012345678901234567890123456789012345678901234" + + echo "✓ courses created" + ''; + }; + + monitoring-install = pkgs.writeShellApplication { + name = "monitoring-install"; + runtimeInputs = [ + pkgs.kubernetes-helm + pkgs.kubectl + ]; + text = '' + helm upgrade --install kube-prometheus-stack prometheus/kube-prometheus-stack \ + --namespace monitoring \ + --create-namespace \ + --wait + kubectl apply -f ./jupyterhub-monitor.yaml + echo "✓ monitoring installed" + kubectl get pods -n monitoring + ''; + }; + + rebuild-all = pkgs.writeShellApplication { + name = "rebuild-all"; + runtimeInputs = [ + pkgs.kind + pkgs.kubectl + pkgs.kubernetes-helm + pkgs.podman + ]; + text = '' + cluster-stop + cluster-start + tls-init + helm-repos + ingress-install + hub-build + notebook-build + db-install + ngshare-install + jupyterhub-install + monitoring-install + echo "Waiting for hub to be ready..." + kubectl wait pod \ + --for=condition=ready \ + --selector=component=hub \ + --namespace jupyterhub \ + --timeout=300s + ngshare-courses + ''; + }; + + in + { + devShells.default = pkgs.mkShell { + name = "kupyter"; + + packages = [ + get-all + cluster-start + cluster-stop + tls-init + helm-repos + ingress-install + jupyterhub-install + notebook-build + db-install + ngshare-install + ngshare-courses + hub-build + monitoring-install + rebuild-all + pkgs.openssl + pkgs.kubectl + pkgs.kubernetes-helm + pkgs.podman + pkgs.kind + ]; + + shellHook = '' + export KUBECONFIG="$(pwd)/.kubeconfig" + export KIND_EXPERIMENTAL_PROVIDER=podman + + echo "" + echo " kupyter dev shell" + echo " KUBECONFIG → $(pwd)/.kubeconfig (session-local)" + echo " runtime → podman (rootless)" + echo "" + echo " cluster-start # create cluster" + echo " cluster-stop # delete cluster + wipe kubeconfig" + echo " tls-init # generate self-signed cert" + echo " helm-repos # add and update helm repos" + echo " ingress-install # install nginx ingress" + echo " notebook-build # build and load notebook image" + echo " jupyterhub-install # install jupyterhub" + echo " get-all # show all pods" + echo "" + ''; + }; + } + ); +} diff --git a/hub-logo-configmap.yaml b/hub-logo-configmap.yaml new file mode 100644 index 0000000..a6c2d69 --- /dev/null +++ b/hub-logo-configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: hub-logos + namespace: jupyterhub +binaryData: + jupyterhub-80.png: "" + jupyterhub.svg: "" diff --git a/jupyterhub-ingress.yaml b/jupyterhub-ingress.yaml new file mode 100644 index 0000000..d182855 --- /dev/null +++ b/jupyterhub-ingress.yaml @@ -0,0 +1,21 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: jupyterhub + namespace: jupyterhub + annotations: + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" +spec: + ingressClassName: nginx + rules: + - host: jupyterhub.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: proxy-public + port: + number: 80 diff --git a/jupyterhub-monitor.yaml b/jupyterhub-monitor.yaml new file mode 100644 index 0000000..f1a203b --- /dev/null +++ b/jupyterhub-monitor.yaml @@ -0,0 +1,20 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: jupyterhub + namespace: monitoring + labels: + release: kube-prometheus-stack +spec: + namespaceSelector: + matchNames: + - jupyterhub + selector: + matchLabels: + component: hub + app: jupyterhub + release: jupyterhub + endpoints: + - port: hub + path: /hub/metrics + interval: 30s diff --git a/kind-config-resolved.yaml b/kind-config-resolved.yaml new file mode 100644 index 0000000..3ce5dc5 --- /dev/null +++ b/kind-config-resolved.yaml @@ -0,0 +1,7 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +name: kupyter +containerdConfigPatches: + - |- + [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] + endpoint = ["http://10.89.2.2:5000"] diff --git a/kind-config.yaml b/kind-config.yaml new file mode 100644 index 0000000..6abb430 --- /dev/null +++ b/kind-config.yaml @@ -0,0 +1,16 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +name: kupyter +nodes: + - role: control-plane + extraPortMappings: + - containerPort: 80 + hostPort: 80 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP +containerdConfigPatches: + - |- + [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] + endpoint = ["http://REGISTRY_IP:5000"] diff --git a/kupyter-notebook/Dockerfile b/kupyter-notebook/Dockerfile new file mode 100644 index 0000000..ef055ab --- /dev/null +++ b/kupyter-notebook/Dockerfile @@ -0,0 +1,9 @@ +FROM jupyter/datascience-notebook:latest + +COPY requirements.txt . +RUN pip install -r requirements.txt + +COPY nbgrader_config.py /etc/jupyter/nbgrader_config.py + +COPY logo.png /opt/conda/lib/python3.13/site-packages/nbclassic/static/base/images/logo.png +COPY logo.png /opt/conda/lib/python3.13/site-packages/jupyter_server/static/logo/logo.png diff --git a/kupyter-notebook/logo.png b/kupyter-notebook/logo.png new file mode 100644 index 0000000..857ca0d Binary files /dev/null and b/kupyter-notebook/logo.png differ diff --git a/kupyter-notebook/logo.svg b/kupyter-notebook/logo.svg new file mode 100644 index 0000000..d491f98 --- /dev/null +++ b/kupyter-notebook/logo.svg @@ -0,0 +1,198 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/kupyter-notebook/nbgrader_config.py b/kupyter-notebook/nbgrader_config.py new file mode 100644 index 0000000..23a9b3d --- /dev/null +++ b/kupyter-notebook/nbgrader_config.py @@ -0,0 +1,11 @@ +from ngshare_exchange import configureExchange + +c = get_config() + +configureExchange( + c, "http://ngshare.jupyterhub.svc.cluster.local:8080/services/ngshare" +) + +c.CourseDirectory.course_id = "*" + +c.FormgradeApp.base_url = "/user/{username}/" diff --git a/kupyter-notebook/requirements.txt b/kupyter-notebook/requirements.txt new file mode 100644 index 0000000..2ef65d8 --- /dev/null +++ b/kupyter-notebook/requirements.txt @@ -0,0 +1,7 @@ +folium +seaborn +#tensorflow +#torch +nbgrader +ngshare_exchange +rise diff --git a/nbgrader_config.hub.py b/nbgrader_config.hub.py new file mode 100644 index 0000000..de1369b --- /dev/null +++ b/nbgrader_config.hub.py @@ -0,0 +1,9 @@ +from ngshare_exchange import configureExchange + +c = get_config() + +configureExchange( + c, "http://ngshare.jupyterhub.svc.cluster.local:8080/services/ngshare" +) + +c.CourseDirectory.course_id = "*" diff --git a/ngshare-values.yaml b/ngshare-values.yaml new file mode 100644 index 0000000..a49eeea --- /dev/null +++ b/ngshare-values.yaml @@ -0,0 +1,6 @@ +pvc: + accessModes: + - ReadWriteOnce + +ngshare: + debug: true diff --git a/tls.crt b/tls.crt new file mode 100644 index 0000000..527e358 --- /dev/null +++ b/tls.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOzCCAiOgAwIBAgIUdweHytW9S6NYnsg8qQC7qkXrSAkwDQYJKoZIhvcNAQEL +BQAwLTEZMBcGA1UEAwwQanVweXRlcmh1Yi5sb2NhbDEQMA4GA1UECgwHa3VweXRl +cjAeFw0yNjAzMjQwMDExMjJaFw0yNzAzMjQwMDExMjJaMC0xGTAXBgNVBAMMEGp1 +cHl0ZXJodWIubG9jYWwxEDAOBgNVBAoMB2t1cHl0ZXIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCsSo1M0tgSqAwEFQ2XY8qJIdhFvJojTj2mK18gizfS +Egn9whTynbKX6+AxRRlH+TQ8TQ91IYXGpZJaAXnxpwwC1X9qjeqrKFoI9927Bgp2 +1CzK8dI2Q7DZPTeSgAAW79dSjApwxqiw+Dy+PP1REe94UvKg/PQezN8Zy3V4+c9F +7noVgUp/xEWPsVwsH84uX2XI7HyHv4t3sMAzqD4d/41GoJX8FZwwK5IKuIyiNVJo +y2UUjcG7qXUUBUXuOcpyW0l9zwRsDtqp6mqXj4+VCdLHKYP/X/mftLK+cwhzNvN5 +uWooZf78igN7rFp1n6cvmIUuQEUnnPGkwBfbZSFK0l7NAgMBAAGjUzBRMB0GA1Ud +DgQWBBRA0biw83Xcib+sQcIypsreXfv9oDAfBgNVHSMEGDAWgBRA0biw83Xcib+s +QcIypsreXfv9oDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB5 +ufcXwO0iKoXkPkAoVXEz4xs0uGQ6Q4nq23VYoNGqKtqlRm7p9FnWv/OFC6p4QXT8 +J1Ju2oa5iQ8iUVvznPEUz78995xMtrnpQbJCHOBvCMpYojNiOmq1d+rxZvzJrVmS +PxMtljbS1Xk60WTmGoWIFjEl8j0TARVzW3tnafEv83Ss/E4oZjo7RVVsyyAa+VQ/ +6kB3HsJRdie+9FpOFU0YzyFuQb+e8mVVUSMwI42WAflDK+PD8ZjyRWVrj+WNWM0b +1Ic1Fe8BzGuDigPc6Md8i09WePyLWw+Q11x2ZCMJzKk4Nh0EPmgemusRUPXBsk0Z +ozVE/zLvqtJg+94F8q9l +-----END CERTIFICATE----- diff --git a/tls.key b/tls.key new file mode 100644 index 0000000..d34a8a3 --- /dev/null +++ b/tls.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsSo1M0tgSqAwE +FQ2XY8qJIdhFvJojTj2mK18gizfSEgn9whTynbKX6+AxRRlH+TQ8TQ91IYXGpZJa +AXnxpwwC1X9qjeqrKFoI9927Bgp21CzK8dI2Q7DZPTeSgAAW79dSjApwxqiw+Dy+ +PP1REe94UvKg/PQezN8Zy3V4+c9F7noVgUp/xEWPsVwsH84uX2XI7HyHv4t3sMAz +qD4d/41GoJX8FZwwK5IKuIyiNVJoy2UUjcG7qXUUBUXuOcpyW0l9zwRsDtqp6mqX +j4+VCdLHKYP/X/mftLK+cwhzNvN5uWooZf78igN7rFp1n6cvmIUuQEUnnPGkwBfb +ZSFK0l7NAgMBAAECggEAAKUpL6g7X4nIvQHm/iwWoHoyGonWwtTPrDJN1deW45qJ +PAq1Dl+0WF0zQNqGEXv5F4K26D1sZVFxIYlsg8sYTBnfvvPmdNUqcMfiSRUjHhNu +gMfdIIPkd1JUxZ+2cdPoLo2KgzHe4cA4lI34LzsfXx9K8HrvGXNV8f1fjUjrR+pX +n2aCTCr+yqvF57ja9JugwOVdpWxfdJK7Km19cEDh78BTwJhmw0dUujh8eFcecxN0 +m9s+HKuOs+TDhl5G2FgfmGxJgz/g5qUDPy+MulPISpQnkFNQ8lYV1FYpCxqsbm+D +L4uGNqvaUbgsl/TDuujcSnFzPNih01D6ZiHESwE/pwKBgQDSGp6RqfA7e9HTIRmW +HORupf1eE/qdIwVWVhBcmHOB6teAY5gtltEnFojOi6+nwm3yuU72pD59S+Ev08g+ +fxdDAdaGR16+9Ih6KvPQNiI+2w9p0huoXIz+kZPGOYrL2d5hsKQrOnNHUEVwHQ4J +BhMrr5Sf4Xjxpv8ATgKyb39CowKBgQDR7WCAFUwvyisVrz1GDAkGzmimbLtWUsSg +dzOX0e54zDf8sC4QJdgowSIsFbfQqfkQQkUJrVA3Lk92XNfuyBo4RS9rnNv0F4eC +4CMFcBsjQ5gIgu5OHbXf1RFoc4fwabetIbb7z3xbmixQfJxgbyzySO8GZ7RwtLN+ +/pVvthtfzwKBgQDHkCM3hmu5hFV7rb/o1o6fDqkHOADeSopiRCMMYH2uVArXV0IP +Y2ZMM1pEnWd99+6JEzyOhtkYF//PduCHhB3rNo62Qooa5JfROoUVKqYCf/427Cv7 +EdWWY14ydSuBjvJsZeS5bq5aeUNLRz2ykoOZBhAsgHRpS86AUpi7Na5x8wKBgGi8 +Dq44cfdR3ScHdAGTlZlQt8N4cgrCZplMf3Aaa+jWsoQefgzOZMcIfH0UJM41Ty6+ +cWU/k8rEDx8VeSIHsZUrZ1pAOzjP2GsCWlanNNLmMV7lu/E7P3c5/WJoaYUXqWz2 +ai29ueSVydAqK3atYPZMTvyaFts4PGl6qKHAcG3fAoGBALYJ7OhtEddzo3PKPZOh +uhH2vyRsLuTZCZYDGPIdxHSS0Kk307oghIZhYwKf+fx5H1Y6xtmZ59QUAg4V2Ol2 +p8uoJW50skVHx/OwZkm+wQkBF+INj4akG/MaFpJ8XcwWtn8FiEMN+ljBJx7nu6dW +GxA2b5RVTQ8SBnF2TEFwjw5o +-----END PRIVATE KEY-----