From 74ad7dd8ff64d7507ce86a8495c9cf8329ba4a1e Mon Sep 17 00:00:00 2001 From: Eli Holmes Date: Sun, 31 Mar 2024 09:04:29 -0700 Subject: [PATCH] add gcp example --- _quarto.yml | 18 +- config/gcp/config.yaml | 27 + docs/ci/iopython/instructions.html | 649 ------------- docs/ci/iorocker/instructions.html | 647 ------------- docs/ci/iosdmTMB/instructions.html | 648 ------------- docs/index.Rmd | 19 + docs/index.html | 572 +---------- docs/{ => posts}/JHub-User-Guide.html | 314 ++++-- docs/{ => posts}/Set-up-centos-security.html | 312 ++++-- docs/{ => posts}/Set-up-centos-tljh.html | 296 +++++- docs/{ => posts}/Set-up-centos.html | 498 +++++++--- docs/{ => posts}/Set-up-daskhub.html | 340 +++++-- docs/{ => posts}/Setup-Notes.html | 289 +++++- .../set-up-authentication.html} | 509 ++++++---- docs/posts/set-up-jh-gcp.html | 899 ++++++++++++++++++ docs/{ => posts}/set-up-vm.html | 302 ++++-- docs/search.json | 822 ++++++++-------- .../bootstrap/bootstrap-dark.min.css | 9 +- docs/site_libs/bootstrap/bootstrap-icons.css | 148 ++- docs/site_libs/bootstrap/bootstrap-icons.woff | Bin 164168 -> 176200 bytes docs/site_libs/bootstrap/bootstrap.min.css | 9 +- docs/site_libs/bootstrap/bootstrap.min.js | 6 +- docs/site_libs/quarto-html/anchor.min.js | 6 +- docs/site_libs/quarto-html/popper.min.js | 4 +- docs/site_libs/quarto-html/quarto.js | 59 +- docs/site_libs/quarto-nav/quarto-nav.js | 8 +- .../quarto-search/autocomplete.umd.js | 4 +- docs/site_libs/quarto-search/quarto-search.js | 155 ++- docs/sitemap.xml | 48 +- posts/JHub-User-Guide.Rmd | 14 +- posts/Set-up-centos-security.Rmd | 4 +- posts/Set-up-centos-tljh.Rmd | 4 +- posts/Set-up-centos.Rmd | 4 +- posts/Set-up-daskhub.Rmd | 34 +- posts/set-up-authentication.Rmd | 103 ++ posts/set-up-jh-gcp.Rmd | 182 ++++ posts/set-up-vm.Rmd | 2 +- 37 files changed, 4278 insertions(+), 3686 deletions(-) create mode 100644 config/gcp/config.yaml delete mode 100644 docs/ci/iopython/instructions.html delete mode 100644 docs/ci/iorocker/instructions.html delete mode 100644 docs/ci/iosdmTMB/instructions.html create mode 100644 docs/index.Rmd rename docs/{ => posts}/JHub-User-Guide.html (67%) rename docs/{ => posts}/Set-up-centos-security.html (73%) rename docs/{ => posts}/Set-up-centos-tljh.html (89%) rename docs/{ => posts}/Set-up-centos.html (83%) rename docs/{ => posts}/Set-up-daskhub.html (75%) rename docs/{ => posts}/Setup-Notes.html (64%) rename docs/{ci/iopython-tf/instructions.html => posts/set-up-authentication.html} (50%) create mode 100644 docs/posts/set-up-jh-gcp.html rename docs/{ => posts}/set-up-vm.html (65%) create mode 100644 posts/set-up-authentication.Rmd create mode 100644 posts/set-up-jh-gcp.Rmd diff --git a/_quarto.yml b/_quarto.yml index 3853938..4e6c888 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -1,7 +1,11 @@ project: type: website output-dir: docs - + render: + - "posts/*.Rmd" + - "!ci/" + - "!config/" + - "!config-template/" website: page-navigation: true title: "Eli's JupyterHub notes" @@ -25,14 +29,16 @@ website: href: index.Rmd - text: "JHub User Guide" href: posts/JHub-User-Guide.Rmd - - text: "JHub Set-up" + - text: "Set-up JHub on Azure" href: posts/Set-up-daskhub.Rmd - - text: "Centos Set-up" + - text: "Set-up JHub on Google Cloud" + href: posts/Set-up-jh-gcp.html + - text: "Set-up JHub on Centos" href: posts/Set-up-centos.Rmd - - text: "Centos Set-up https" + - text: "Set-up https on Centos" href: posts/Set-up-centos-security.Rmd - - text: "Set-up VM on mac" - href: posts/set-up-vm.Rmd + - text: "Set-up authentication" + href: posts/set-up-authentication.Rmd format: html: diff --git a/config/gcp/config.yaml b/config/gcp/config.yaml new file mode 100644 index 0000000..888d112 --- /dev/null +++ b/config/gcp/config.yaml @@ -0,0 +1,27 @@ +hub: + config: + GitHubOAuthenticator: + client_id: 5872d2b7ea348ff2f8c0 + client_secret: 6dafc2820264b4d8602e632e891f078481d4927a + oauth_callback_url: https://gcp.opensci.live/hub/oauth_callback + allowed_organizations: + - nmfs-opensci:DaskHub + scope: + - read:org + JupyterHub: + authenticator_class: github + +proxy: + traefik: + extraInitContainers: + # This startup delay can help the k8s container network find the + # https certificate and allow letsencrypt to work + - name: startup-delay + image: busybox:stable + command: ["sh", "-c", "sleep 10"] + https: + enabled: true + hosts: + - gcp.opensci.live + letsencrypt: + contactEmail: eli.holmes@noaa.gov \ No newline at end of file diff --git a/docs/ci/iopython/instructions.html b/docs/ci/iopython/instructions.html deleted file mode 100644 index 71397ae..0000000 --- a/docs/ci/iopython/instructions.html +++ /dev/null @@ -1,649 +0,0 @@ - - - - - - - - - -Eli's JupyterHub notes – instructions - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- -
- - -
- - - -
- - - -
-

Indian Ocean Summer Docker Images: Openscapes + a few extras

-

https://hub.docker.com/repository/docker/eeholmes/iopython/general

-

The one to use is the dated one. The main tag doesn’t seem to always be recognized as a new tag when it changes.

-
-
-

Requirements

-

Docker installed. For example, if doing on a Mac or PC, you need Docker Desktop. On VMs, docker will already be installed.

-

A DockerHub user account. The instructions are using EEH’s.

-
-
-

Add new packages

-

Add to Dockerfile something like

-
RUN conda install -c conda-forge cmocean
-RUN pip install cmocean
-

Try to use conda instead of pip so that any package conflicts are resolved.

-
-
-

Rebuild and push the Docker image

-
    -
  1. Make sure Docker app is running, not just installed. So if you are on a local computer, start up the app (open it).
  2. -
  3. Go to a terminal and cd to the directory with the Dockerfile. So to the ci directory in the nmfs-jhub repo.
  4. -
-
cd ci/iopython
-
    -
  1. Update the docker tag to the date.
  2. -
-
DOCKER_TAG="20230901"
-
    -
  1. Build the image. . means current directory. eeholmes/iopython is the name of the repo on DockerHub. See notes below.
  2. -
-
docker build --platform linux/amd64 -t eeholmes/iopython:${DOCKER_TAG} -t eeholmes/iopython:main .
-
    -
  1. Push the image up to DockerHub. Make sure you are logged into DockerHub in the Docker app otherwise you’ll get “access denied”. Open the Docker app and look that it shows that you are signed in.
  2. -
-
docker push eeholmes/iopython:${DOCKER_TAG}
-docker push eeholmes/iopython:main
-

Notes: https://help.valohai.com/hc/en-us/articles/4421364087569-Build-your-own-Docker-image

-
-
-

Stop any running the Jupyter Lab instances

-

Log in. File > Hub Control > Stop my server

-
-
-

Run helm upgrade

-

Log into Azure portal, go to DaskHub, Connect, Cloud Shell, and run this command. Note, this assumes that eeholmes/iopython:main is still the image to use. If not, edit dconfig2.yaml (nano dconfig2.yaml) and then upgrade.

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-
-
-

Asides

-
-

To set docker tag to latest commit

-
SHA7="$(git rev-parse --short HEAD)"
-DOCKER_TAG=$SHA7
-

I am not doing that since this repo has lots of commits unrelated to the docker image.

-
-
-

To set up your own Docker repo

-
    -
  1. Make an account on DockerHub. Free is fine.
  2. -
  3. Create a repo and give it a name. For example, for this project, my account is eeholmes and my repo is iopython (Indian Ocean Python) as it is specific a particular project I am working on.
  4. -
-

DockerHub will want to you to buy the premium account but you only need that if you are doing continuous integration, like using a GitHub Action to autobuild your image. If you are manually building, you don’t need this.

-
-
-

Why is --platform needed in the build command

-

You won’t see this on docker build tutorials. But if you are on a Mac with Apple chip, then you’ll build arm64 images and that’s not going to work on Ubuntu VMs. The vanilla images you see are amd64 so we want to make sure we are building for that platform. This only matters if you are on a Mac with Apple chip, but it won’t break things for unix and PC so I added to make the instructions more robust.

-
-
-

If a specific image tag is in config

-

The JupyterHub has a config file that specifies what images are being used. If the image is say eeholmes/iopython:hublatest, then whenever the a image with tag hublatest is pushed, the hub will use that. If on the otherhand, you config file has a specific, an unique tag that you don’t overwrite, then you’ll have to update the file in the config file on the cluster (log into Azure, go to cluster, connect to cloud shell, nano dconfig2.yaml) and upgrade the installation of the JupyterHub.

-

Why not eeholmes/iopython:latest? There is nothing special about latest. It is the default tag used if you don’t specify -t and : in your build call. So it is a bit too easy to accidentally update “latest” and thus update the image for you hub when you didn’t intend to do that. You just forgot to specify a tag.

-

To update if you are using a specific tag, like 20230615 rather than one you keep updating like hublatest or latest:

-
-

Step 1

-

Edit the config file. Mine is called dconfig2.yaml. Yours is probably config.yaml. Name is unimportant.

-
nano dconfig2.yaml
-

Inside dconfig2.yaml is this info. This shows a fixed tag. So if I update, I need to change the 20230615 part.

-
  singleuser:
-    image:
-      name: eeholmes/iopython
-      tag: 20230615
-

Save the changes. In nano, it cmd-O, return, cmd-X.

-
-
-

run helm upgrade

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-
-
-

The helm upgrade command

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-

A helm is what runs the commands to upgrade (and install in the beginning) our JupyterHub. dask/daskhub is point to the repo with the “helm chart” (the instructions). --value dconfig2.yaml is telling it where the config file is.

-
    -
  • upgrade upgrade an existing installation with the values in dconfig2.yaml
  • -
  • --render-subchart-notes the dask/daskhub helm chart has subcharts (jupyterhub) and you need to render these too. Not all helm charts have this.
  • -
  • dask/daskhub the name of the repo that has the helm chart. The first time you reference this, you need to tell help about the repo by giving it the url. Read how here
  • -
  • --version=2023.1.0 version of the helm chart. Update when the helm chart (instructions for installing the jupyterhub) changes.
  • -
-
-
-
-

Adding packages with newpackages.yml

-

When the openscapes image is used, we are in a conda env called ‘notebook’. We want to update that with the packages in newpackages.yml but need to get that file into the container. For now, I just hard code in the pip/conda install commands.

-

Add to Docker file

-
# it can't find new.yml in home/joyvan/.kernels
-# need to get that into the container somehow (git clone?)
-# RUN conda env update --file new.yml
- - -
-
- -
- -
- - - - - \ No newline at end of file diff --git a/docs/ci/iorocker/instructions.html b/docs/ci/iorocker/instructions.html deleted file mode 100644 index 79bb4c3..0000000 --- a/docs/ci/iorocker/instructions.html +++ /dev/null @@ -1,647 +0,0 @@ - - - - - - - - - -Eli's JupyterHub notes – instructions - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- -
- - -
- - - -
- - - -
-

Indian Ocean Summer Docker Images

-

https://hub.docker.com/repository/docker/eeholmes/iorocker/general

-

The one to use is the dated one. The main tag doesn’t seem to always be recognized as a new tag when it changes.

-
-
-

Requirements

-

Docker installed. For example, if doing on a Mac or PC, you need Docker Desktop. On VMs, docker will already be installed.

-

A DockerHub user account. The instructions are using EEH’s.

-
-
-

Add new packages

-

Add to Dockerfile something like

-
RUN R -e 'install.packages("gtools", repos = "http://cran.us.r-project.org")'
-
-
-

Rebuild and push the Docker image

-
    -
  1. Make sure Docker app is running, not just installed. So if you are on a local computer, start up the app (open it).
  2. -
  3. Go to a terminal and cd to the directory with the Dockerfile. So to the ci directory in the nmfs-jhub repo.
  4. -
-
cd ci/iorocker
-
    -
  1. Update the docker tag to the date.
  2. -
-
DOCKER_TAG="20230901"
-
    -
  1. Build the image. . means current directory. eeholmes/iorocker is the name of the repo on DockerHub. --platform is added if you are building on an Mac with Apple chip.
  2. -
-
docker build --platform linux/amd64 -t eeholmes/iorocker:${DOCKER_TAG} -t eeholmes/iorocker:main .
-
    -
  1. Push the image up to DockerHub. Make sure you are logged into DockerHub in the Docker app otherwise you’ll get “access denied”. Open the Docker app and look that it shows that you are signed in.
  2. -
-
docker push eeholmes/iorocker:${DOCKER_TAG}
-docker push eeholmes/iorocker:main
-

Notes: https://help.valohai.com/hc/en-us/articles/4421364087569-Build-your-own-Docker-image

-
-
-

Stop any running the Jupyter Lab instances

-

Log in. File > Hub Control > Stop my server

-
-
-

Run helm upgrade

-

Log into Azure portal, go to DaskHub, Connect, Cloud Shell, and run this command. Note, this assumes that eeholmes/iorocker:main is still the image to use. If not, edit dconfig2.yaml (nano dconfig2.yaml) and then upgrade.

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-
-
-

Asides

-
-

To set docker tag to latest commit

-
SHA7="$(git rev-parse --short HEAD)"
-DOCKER_TAG=$SHA7
-

I am not doing that since this repo has lots of commits unrelated to the docker image.

-
-
-

To set up your own Docker repo

-
    -
  1. Make an account on DockerHub. Free is fine.
  2. -
  3. Create a repo and give it a name. For example, for this project, my account is eeholmes and my repo is iopython (Indian Ocean Python) as it is specific a particular project I am working on.
  4. -
-

DockerHub will want to you to buy the premium account but you only need that if you are doing continuous integration, like using a GitHub Action to autobuild your image. If you are manually building, you don’t need this.

-
-
-

Why is --platform needed in the build command

-

You won’t see this on docker build tutorials. But if you are on a Mac with Apple chip, then you’ll build arm64 images and that’s not going to work on Ubuntu VMs. The vanilla images you see are amd64 so we want to make sure we are building for that platform. This only matters if you are on a Mac with Apple chip, but it won’t break things for unix and PC so I added to make the instructions more robust.

-
-
-

If an specific image tag is in config

-

The JupyterHub has a config file that specifies what images are being used. If the image is say eeholmes/iopython:hublatest, then whenever the a image with tag hublatest is pushed, the hub will use that. If on the otherhand, you config file has a specific, an unique tag that you don’t overwrite, then you’ll have to update the file in the config file on the cluster (log into Azure, go to cluster, connect to cloud shell, nano dconfig2.yaml) and upgrade the installation of the JupyterHub.

-

Why not eeholmes/iopython:latest? There is nothing special about latest. It is the default tag used if you don’t specify -t and : in your build call. So it is a bit too easy to accidentally update “latest” and thus update the image for you hub when you didn’t intend to do that. You just forgot to specify a tag.

-

To update if you are using a specific tag, like 20230615 rather than one you keep updating like hublatest or latest:

-
-

Step 1

-

Edit the config file. Mine is called dconfig2.yaml. Yours is probably config.yaml. Name is unimportant.

-
nano dconfig2.yaml
-

Inside dconfig2.yaml is this info. This shows a fixed tag. So if I update, I need to change the 20230615 part.

-
  singleuser:
-    image:
-      name: eeholmes/iopython
-      tag: 20230615
-

Save the changes. In nano, it cmd-O, return, cmd-X.

-
-
-

run helm upgrade

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-
-
-

The helm upgrade command

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-

A helm is what runs the commands to upgrade (and install in the beginning) our JupyterHub. dask/daskhub is point to the repo with the “helm chart” (the instructions). --value dconfig2.yaml is telling it where the config file is.

-
    -
  • upgrade upgrade an existing installation with the values in dconfig2.yaml
  • -
  • --render-subchart-notes the dask/daskhub helm chart has subcharts (jupyterhub) and you need to render these too. Not all helm charts have this.
  • -
  • dask/daskhub the name of the repo that has the helm chart. The first time you reference this, you need to tell help about the repo by giving it the url. Read how here
  • -
  • --version=2023.1.0 version of the helm chart. Update when the helm chart (instructions for installing the jupyterhub) changes.
  • -
-
-
-
-

Adding packages with newpackages.yml

-

When the openscapes image is used, we are in a conda env called ‘notebook’. We want to update that with the packages in newpackages.yml but need to get that file into the container. For now, I just hard code in the pip install commands.

-

Add to Docker file

-
# it can't find new.yml in home/joyvan/.kernels
-# need to get that into the container somehow (git clone?)
-# RUN conda env update --file new.yml
- - -
-
- -
- -
- - - - - \ No newline at end of file diff --git a/docs/ci/iosdmTMB/instructions.html b/docs/ci/iosdmTMB/instructions.html deleted file mode 100644 index d4e9b90..0000000 --- a/docs/ci/iosdmTMB/instructions.html +++ /dev/null @@ -1,648 +0,0 @@ - - - - - - - - - -Eli's JupyterHub notes – instructions - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- -
- - -
- - - -
- - - -
-

Indian Ocean Summer Docker Images

-

https://hub.docker.com/repository/docker/eeholmes/iosdmTMB/general

-

A separate sdmTMB image is made since it takes forever to build.

-
-
-

Requirements

-

Docker installed. For example, if doing on a Mac or PC, you need Docker Desktop. On VMs, docker will already be installed.

-

A DockerHub user account. The instructions are using EEH’s.

-
-
-

Add new packages

-

Add to Dockerfile something like

-
RUN R -e 'install.packages("sdmTMB", dependencies = TRUE)'
-
-
-

Rebuild and push the Docker image

-
    -
  1. Make sure Docker app is running, not just installed. So if you are on a local computer, start up the app (open it).
  2. -
  3. Go to a terminal and cd to the directory with the Dockerfile. So to the ci directory in the nmfs-jhub repo.
  4. -
-
cd ci/iosdmTMB
-
    -
  1. Update the docker tag to the date.
  2. -
-
DOCKER_TAG="20230615"
-
    -
  1. Build the image. . means current directory. eeholmes/iorocker is the name of the repo on DockerHub. --platform is added if you are building on an Mac with Apple chip.
  2. -
-
docker build --platform linux/amd64 -t eeholmes/iosdmTMB:${DOCKER_TAG} -t eeholmes/iosdmTMB:main .
-
    -
  1. Push the image up to DockerHub. Make sure you are logged into DockerHub in the Docker app otherwise you’ll get “access denied”. Open the Docker app and look that it shows that you are signed in.
  2. -
-
docker push eeholmes/iosdmTMB:${DOCKER_TAG}
-docker push eeholmes/iosdmTMB:main
-

Notes: https://help.valohai.com/hc/en-us/articles/4421364087569-Build-your-own-Docker-image

-
-
-

Stop any running the Jupyter Lab instances

-

Log in. File > Hub Control > Stop my server

-
-
-

Run helm upgrade

-

Log into Azure portal, go to DaskHub, Connect, Cloud Shell, and run this command. Note, this assumes that eeholmes/iorocker:main is still the image to use. If not, edit dconfig2.yaml (nano dconfig2.yaml) and then upgrade.

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-

It can take 30 minutes for the changes to take place.

-
-
-

Asides

-
-

To set docker tag to latest commit

-
SHA7="$(git rev-parse --short HEAD)"
-DOCKER_TAG=$SHA7
-

I am not doing that since this repo has lots of commits unrelated to the docker image.

-
-
-

To set up your own Docker repo

-
    -
  1. Make an account on DockerHub. Free is fine.
  2. -
  3. Create a repo and give it a name. For example, for this project, my account is eeholmes and my repo is iopython (Indian Ocean Python) as it is specific a particular project I am working on.
  4. -
-

DockerHub will want to you to buy the premium account but you only need that if you are doing continuous integration, like using a GitHub Action to autobuild your image. If you are manually building, you don’t need this.

-
-
-

Why is --platform needed in the build command

-

You won’t see this on docker build tutorials. But if you are on a Mac with Apple chip, then you’ll build arm64 images and that’s not going to work on Ubuntu VMs. The vanilla images you see are amd64 so we want to make sure we are building for that platform. This only matters if you are on a Mac with Apple chip, but it won’t break things for unix and PC so I added to make the instructions more robust.

-
-
-

If an specific image tag is in config

-

The JupyterHub has a config file that specifies what images are being used. If the image is say eeholmes/iopython:hublatest, then whenever the a image with tag hublatest is pushed, the hub will use that. If on the otherhand, you config file has a specific, an unique tag that you don’t overwrite, then you’ll have to update the file in the config file on the cluster (log into Azure, go to cluster, connect to cloud shell, nano dconfig2.yaml) and upgrade the installation of the JupyterHub.

-

Why not eeholmes/iopython:latest? There is nothing special about latest. It is the default tag used if you don’t specify -t and : in your build call. So it is a bit too easy to accidentally update “latest” and thus update the image for you hub when you didn’t intend to do that. You just forgot to specify a tag.

-

To update if you are using a specific tag, like 20230615 rather than one you keep updating like hublatest or latest:

-
-

Step 1

-

Edit the config file. Mine is called dconfig2.yaml. Yours is probably config.yaml. Name is unimportant.

-
nano dconfig2.yaml
-

Inside dconfig2.yaml is this info. This shows a fixed tag. So if I update, I need to change the 20230615 part.

-
  singleuser:
-    image:
-      name: eeholmes/iopython
-      tag: 20230615
-

Save the changes. In nano, it cmd-O, return, cmd-X.

-
-
-

run helm upgrade

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-
-
-

The helm upgrade command

-
helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
-

A helm is what runs the commands to upgrade (and install in the beginning) our JupyterHub. dask/daskhub is point to the repo with the “helm chart” (the instructions). --value dconfig2.yaml is telling it where the config file is.

-
    -
  • upgrade upgrade an existing installation with the values in dconfig2.yaml
  • -
  • --render-subchart-notes the dask/daskhub helm chart has subcharts (jupyterhub) and you need to render these too. Not all helm charts have this.
  • -
  • dask/daskhub the name of the repo that has the helm chart. The first time you reference this, you need to tell help about the repo by giving it the url. Read how here
  • -
  • --version=2023.1.0 version of the helm chart. Update when the helm chart (instructions for installing the jupyterhub) changes.
  • -
-
-
-
-

Adding packages with newpackages.yml

-

When the openscapes image is used, we are in a conda env called ‘notebook’. We want to update that with the packages in newpackages.yml but need to get that file into the container. For now, I just hard code in the pip install commands.

-

Add to Docker file

-
# it can't find new.yml in home/joyvan/.kernels
-# need to get that into the container somehow (git clone?)
-# RUN conda env update --file new.yml
- - -
-
- -
- -
- - - - - \ No newline at end of file diff --git a/docs/index.Rmd b/docs/index.Rmd new file mode 100644 index 0000000..c769727 --- /dev/null +++ b/docs/index.Rmd @@ -0,0 +1,19 @@ +--- +title: "NMFS OpenSci JupyterHub Notes" +--- + +This page shows my notes for setting up JupyterHubs on various platforms. What is a JupyterHub? Read about why cloud computing environments are great: [SnowEx 2022](https://snowex-2022.hackweek.io/preliminary/jupyterhub.html) + +## Test JHub + +I have set us up a JHub with RStudio on Azure. It's on Kubernetes and will spin up VMs as needed. The VMs are not huge: 2CPU & 8 Gig RAM. + + + +Contact Eli if you want to test it out. See instructions under [JHub User Guide](JHub-User-Guide.html) + +## Installation instructions + +The other tabs to the left are my JHub installation notes for various platforms. + + diff --git a/docs/index.html b/docs/index.html index a638fde..3a849a4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,564 +1,8 @@ - - - - - - - - - -Eli’s JupyterHub notes - NMFS OpenSci JupyterHub Notes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- -
- - -
- - - -
- -
-
-

NMFS OpenSci JupyterHub Notes

-
- - - -
- - - - -
- - -
- -

This page shows my notes for setting up JupyterHubs on various platforms. What is a JupyterHub? Read about why cloud computing environments are great: SnowEx 2022

-
-

Test JHub

-

I have set us up a JHub with RStudio on Azure. It’s on Kubernetes and will spin up VMs as needed. The VMs are not huge: 2CPU & 8 Gig RAM.

-

https://jhub.opensci.live/hub/login

-

Contact Eli if you want to test it out. See instructions under JHub User Guide

-
-
-

Installation instructions

-

The other tabs to the left are my JHub installation notes for various platforms.

- - -
- -
- - -
-
- -
- - - - \ No newline at end of file + + + Redirect to posts/JHub-User-Guide.html + + + + + diff --git a/docs/JHub-User-Guide.html b/docs/posts/JHub-User-Guide.html similarity index 67% rename from docs/JHub-User-Guide.html rename to docs/posts/JHub-User-Guide.html index c1082a0..89087b3 100644 --- a/docs/JHub-User-Guide.html +++ b/docs/posts/JHub-User-Guide.html @@ -2,7 +2,7 @@ - + @@ -24,33 +24,39 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -77,10 +84,10 @@ - - - @@ -91,8 +98,8 @@ +
@@ -183,8 +196,10 @@

JHub User Instructions

+ +

I have set us up a JupyterHub/RStudio cloud-computing hub on Azure. It’s on Kubernetes and will spin up VMs as needed. The VMs are not huge: 2CPU & 8 Gig RAM.

https://jhub.opensci.live/hub/login

Login authentication is via GitHub. Only members of the JHub GitHub team can log on. This is a testing environment. Contact Eli if you want to test it out.

@@ -199,25 +214,25 @@

JHub User Instructions

Login

After you get paste the login page, you will see this. Choose Python for Python only; choose R for Python and R.

-

+

Spin up your server

You will see this as your server spins up

-

+

Choose your platform

You can code in RStudio, JupyterLab or Terminal.

-

+

Let’s choose RStudio

-

+

Clone a repo

Choose ‘new project’ (top right) and Version Control.

-

+

Tell Git who you are

@@ -243,7 +258,7 @@

Let’s choose Jupy

Tell Git who you are

But I just did that with RStudio! I know but the JLab instance is in a different environment and doesn’t know what you did in the RStudio environment.

Open a terminal. You do this from the Launcher window. You can always open a new launcher window by clicking the little + tab

-

+

Now click Terminal and run this code

git config --global user.name "YourName"
 git config --global user.email "your@email.com"
@@ -256,7 +271,7 @@ 

Tell Git who you ar

Clone a Git repo

Click the Git icon on the left and you can clone a repo.

-

+

@@ -310,6 +325,33 @@

Stop your server

} } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -376,13 +418,15 @@

Stop your server

return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -461,10 +505,9 @@

Stop your server

// clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -474,8 +517,17 @@

Stop your server

interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -489,6 +541,128 @@

Stop your server

const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -531,6 +705,7 @@

Stop your server

} div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -556,6 +731,32 @@

Stop your server

}); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -617,13 +818,13 @@

Stop your server

@@ -633,7 +834,9 @@

Stop your server

- + @@ -642,4 +845,5 @@

Stop your server

+ \ No newline at end of file diff --git a/docs/Set-up-centos-security.html b/docs/posts/Set-up-centos-security.html similarity index 73% rename from docs/Set-up-centos-security.html rename to docs/posts/Set-up-centos-security.html index 2f18739..72d845b 100644 --- a/docs/Set-up-centos-security.html +++ b/docs/posts/Set-up-centos-security.html @@ -2,7 +2,7 @@ - + @@ -23,7 +23,7 @@ } /* CSS for syntax highlighting */ pre > code.sourceCode { white-space: pre; position: relative; } -pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } +pre > code.sourceCode > span { line-height: 1.25; } pre > code.sourceCode > span:empty { height: 1.2em; } .sourceCode { overflow: visible; } code.sourceCode > span { color: inherit; text-decoration: inherit; } @@ -58,33 +58,39 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -111,10 +118,10 @@ - - - @@ -125,8 +132,8 @@ +
@@ -215,8 +228,10 @@

Set-up CentOS https

+ +

Now that our basic JupyterHub is running, we want to secure it. We are going to use Let’s Encrypt. Prerequisites:

  • We need to have set up a domain (URL) that points to the public IP of our JupyterHub
  • @@ -235,12 +250,12 @@

    Create a domain name<

    Create a DNS entry

    Let’s pretend you set up bluemountain123.live as the domain. Go to the DNS settings for your domain. Add a type A record. This will do 2 things. First this will create the subdomain that you will use to access your JupyterHub. So let’s say you create, dhub as the type A DNS entry. Put dhub in the name and the public IP address of the server (leaving off :8000) in the value section. Then dhub.bluemountain123.live will be the url.

    -

    +

    Test if the url is working

    -

    http:\\dhub.bluemountain123.live:8000 would be the url using the example domain above. Test that it is working (shows a JupyterHub login) before moving on. This is what you should see:

    -

    +

    http://dhub.bluemountain123.live:8000 would be the url using the example domain above. Test that it is working (shows a JupyterHub login) before moving on. This is what you should see:

    +

@@ -333,7 +348,7 @@

Restart and Test

sudo systemctl start jupyterhub.service
-

Try https:\\dhub.bluemountain123.live and you should see the JupyterHub login without the http warning.

+

Try https://dhub.bluemountain123.live and you should see the JupyterHub login without the http warning.

@@ -383,6 +398,33 @@

Restart and Test

} } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -449,13 +491,15 @@

Restart and Test

return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -534,10 +578,9 @@

Restart and Test

// clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -547,8 +590,17 @@

Restart and Test

interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -562,6 +614,128 @@

Restart and Test

const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -604,6 +778,7 @@

Restart and Test

} div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -629,6 +804,32 @@

Restart and Test

}); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -690,13 +891,13 @@

Restart and Test

@@ -706,7 +907,9 @@

Restart and Test

- + @@ -715,4 +918,5 @@

Restart and Test

+ \ No newline at end of file diff --git a/docs/Set-up-centos-tljh.html b/docs/posts/Set-up-centos-tljh.html similarity index 89% rename from docs/Set-up-centos-tljh.html rename to docs/posts/Set-up-centos-tljh.html index 078829d..b99b3de 100644 --- a/docs/Set-up-centos-tljh.html +++ b/docs/posts/Set-up-centos-tljh.html @@ -2,7 +2,7 @@ - + @@ -23,7 +23,7 @@ } /* CSS for syntax highlighting */ pre > code.sourceCode { white-space: pre; position: relative; } -pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } +pre > code.sourceCode > span { line-height: 1.25; } pre > code.sourceCode > span:empty { height: 1.2em; } .sourceCode { overflow: visible; } code.sourceCode > span { color: inherit; text-decoration: inherit; } @@ -58,31 +58,37 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -109,10 +116,10 @@ - - - @@ -123,8 +130,8 @@ +
@@ -223,8 +236,10 @@

Centos Set-up with TLJH

+ +

This is my notes for setting this up on a Centos 8 (Linux distribution) server. Jump to the “Summary” section to see only the instructions without explanations.

All the commands are run in a shell (bash)

References:

@@ -666,12 +681,12 @@

Create a domain name<

Create a DNS entry

Let’s pretend you set up bluemountain123.live as the domain. Go to the DNS settings for your domain. Add a type A record. This will do 2 things. First this will create the subdomain that you will use to access your JupyterHub. So let’s say you create, dhub as the type A DNS entry. Put dhub in the name and the public IP address of the server (leaving off :8000) in the value section. Then dhub.bluemountain123.live will be the url.

-

+

Test if the url is working

http:\\dhub.bluemountain123.live:8000 would be the url using the example domain above. Test that it is working (shows a JupyterHub login) before moving on. This is what you should see:

-

+

Set-up https on your JupyterHub

@@ -871,6 +886,33 @@

Summary

} } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -937,13 +979,15 @@

Summary

return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -1022,10 +1066,9 @@

Summary

// clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -1035,8 +1078,17 @@

Summary

interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -1050,6 +1102,128 @@

Summary

const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -1092,6 +1266,7 @@

Summary

} div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -1117,6 +1292,32 @@

Summary

}); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1182,7 +1383,9 @@

Summary

- + @@ -1191,4 +1394,5 @@

Summary

+ \ No newline at end of file diff --git a/docs/Set-up-centos.html b/docs/posts/Set-up-centos.html similarity index 83% rename from docs/Set-up-centos.html rename to docs/posts/Set-up-centos.html index a7a57b8..6249352 100644 --- a/docs/Set-up-centos.html +++ b/docs/posts/Set-up-centos.html @@ -2,7 +2,7 @@ - + @@ -23,7 +23,7 @@ } /* CSS for syntax highlighting */ pre > code.sourceCode { white-space: pre; position: relative; } -pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } +pre > code.sourceCode > span { line-height: 1.25; } pre > code.sourceCode > span:empty { height: 1.2em; } .sourceCode { overflow: visible; } code.sourceCode > span { color: inherit; text-decoration: inherit; } @@ -58,33 +58,39 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -111,10 +118,10 @@ - - - @@ -125,8 +132,8 @@ +
@@ -225,8 +238,10 @@

Centos Set-up

+ +

This is my notes for setting this up on a Centos 8 (Linux distribution) server. Jump to the “Summary” section to see only the instructions without explanations.

All the commands are run in a shell (bash)

References:

@@ -648,7 +663,7 @@

Creating a shared

One read-only shared volume:

https://github.com/jupyterhub/dockerspawner/issues/172

-
c.DockerSpawner.volumes = {  'jupyterhub-user-{username}':'/home/jovyan',   '/path/to/shared': {"bind": '/home/jovyan/shared', "mode": "ro"} }
+
c.DockerSpawner.volumes = {  'jupyterhub-{username}':'/home/jovyan',   '/path/to/shared': {"bind": '/home/jovyan/shared', "mode": "ro"} }

A volume that is read-only for some and read-write for others:

https://github.com/jupyterhub/dockerspawner/issues/172

@@ -659,7 +674,7 @@

Creating a shared

Setting up https

If you are using a public IP address, rather than being on a private network, you need to set up https so that content (passwords and everything else) is not visible. Read how to do that here.

-

These instructions set up this url: https:\\dhub.bluemountain123.live

+

These instructions set up this url: https://dhub.bluemountain123.live

GitHub authentication

@@ -670,24 +685,52 @@

C

This Oauth application is going to be associated with your (personal) GitHub account, but you will use a team on a GitHub organization that you are owner of for the users who are allowed to log into your JupyterHub.

Log into GitHub and go to GitHub > Settings > Developer Settings > New Oauth Application

Look carefully at how I filled in the boxes. Change the URL and the name of the application.

-

+

Next you will see something like this

-

+

You need to copy the ID and then click the create secrets button and save the secret. You will need those in the next step.

Create a team in your GitHub organization

You will be added by default and add anyone else who needs access to the hub. Let’s say your GitHub organization is MyOrg and the team is called JHub. So then the allowed organization is MyOrg:JHub. You can leave off :JHub if you want to allow all members of the organization to log in.

+
+

Install

+

Install the oauthenticator package. Make sure you are in the jupyterhub conda environment.

+
+
# check what environment you are in and switch if needed
+# conda env list
+# conda activate jupyterhub
+conda install -c conda-forge oauthenticator
+
+

Edit the jupyterhub_config.py file

+

Edit with something like

+
+
cd /opt/miniconda3/envs/jupyterhub/etc/jupyterhub/
+nano jupyterhub_config.py
+
+

Add these info. Replace the id, secret, url and admin user with your values. Adding an admin user is handy because then you can do some basic management of the hub. Read more here.

c.JupyterHub.authenticator_class = "github"
-c.OAuthenticator.oauth_callback_url = "https:\\dhub.bluemountain123.live/hub/oauth_callback"
+c.OAuthenticator.oauth_callback_url = "https://dhub.bluemountain123.live/hub/oauth_callback"
 c.OAuthenticator.client_id = "your oauth2 application id"
 c.OAuthenticator.client_secret = "your oauth2 application secret"
-c.GitHubOAuthenticator.allowed_organizations = "MyOrg:JHub"
-c.GitHubOAuthenticator.scope = "read:org"
-

Replace the id, secret and url with your values.

+c.GitHubOAuthenticator.allowed_organizations = {"MyOrg:JHub"} +c.GitHubOAuthenticator.scope = ["read:org"] +c.GitHubOAuthenticator.admin_users = {"eeholmes"}
+
+
+

Restart the hub

+
+
sudo systemctl stop jupyterhub.service
+sudo systemctl start jupyterhub.service
+
+

Now any member you add to the GitHub organization team should be able to log in.

+

If you run into trouble, try

+
+
sudo systemctl status jupyterhub.service
+

@@ -695,120 +738,120 @@

Summary

Only the instructions. Make sure you are installing as the root user. I assume you have Python and conda installed.

Create the conda environment

-
sudo -i
-
-conda create -n jupyterhub python --yes
-conda activate jupyterhub
-conda install -c conda-forge jupyterhub --yes
-conda install -c conda-forge jupyterlab notebook --yes
-
-JHUBENV=/opt/miniconda3/envs/jupyterhub
-chmod 755 $JHUBENV
+
sudo -i
+
+conda create -n jupyterhub python --yes
+conda activate jupyterhub
+conda install -c conda-forge jupyterhub --yes
+conda install -c conda-forge jupyterlab notebook --yes
+
+JHUBENV=/opt/miniconda3/envs/jupyterhub
+chmod 755 $JHUBENV

Create user

-
useradd jhub
+
useradd jhub

Open the 8000 port for access to the application.

-
#sudo systemctl enable firewalld
-#sudo systemctl start firewalld
-
-sudo firewall-cmd  --permanent --add-port 8000/tcp
-sudo firewall-cmd --reload
-sudo firewall-cmd --list-ports
+
#sudo systemctl enable firewalld
+#sudo systemctl start firewalld
+
+sudo firewall-cmd  --permanent --add-port 8000/tcp
+sudo firewall-cmd --reload
+sudo firewall-cmd --list-ports

Create the configuration file. Will be edited at end.

-
sudo mkdir -p $JHUBENV/etc/jupyterhub/
-cd $JHUBENV/etc/jupyterhub/
-sudo $JHUBENV/bin/jupyterhub --generate-config
+
sudo mkdir -p $JHUBENV/etc/jupyterhub/
+cd $JHUBENV/etc/jupyterhub/
+sudo $JHUBENV/bin/jupyterhub --generate-config

Install docker if needed

-
sudo yum install -y yum-utils
-sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
-
-sudo systemctl start docker
+
sudo yum install -y yum-utils
+sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
+
+sudo systemctl start docker

Not sure this is needed.

-
sudo firewall-cmd --zone=docker --add-port=8081/tcp
-sudo firewall-cmd --reload
-sudo systemctl restart docker
+
sudo firewall-cmd --zone=docker --add-port=8081/tcp
+sudo firewall-cmd --reload
+sudo systemctl restart docker

Install dockerspawner

-
conda install -c conda-forge dockerspawner --yes
-conda install -c conda-forge docker-py --yes
+
conda install -c conda-forge dockerspawner --yes
+conda install -c conda-forge docker-py --yes

Edit the configuration file.

-
cd $JHUBENV/etc/jupyterhub/
-nano jupyterhub_config.py
+
cd $JHUBENV/etc/jupyterhub/
+nano jupyterhub_config.py

Paste this in

-
# Configuration file for jupyterhub.
-
-c = get_config()  #noqa
-c.JupyterHub.port = 8000
-c.JupyterHub.hub_bind_url = "http://0.0.0.0:8081"
-c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
-c.DockerSpawner.remove = True
-c.Spawner.http_timeout = 3600
-c.DockerSpawner.image_whitelist = {
-    'iorocker': 'eeholmes/iorocker-standalone:20231003',
-    'rocker-binder': 'eeholmes/rocker-binder:20231003',
-    'openscapes-rocker': 'eeholmes/minimal-jhub:20231004',
-    'datascience-r': 'jupyter/datascience-notebook:r-4.3.1',
-    'scipy-notebook': 'jupyter/scipy-notebook:7e1a19a8427f',
-}
-
-notebook_dir = '/home/jovyan'
-c.DockerSpawner.notebook_dir = notebook_dir
-
-# Mount the real user's Docker volume on the host to the notebook user's
-# notebook directory in the container
-c.DockerSpawner.volumes = { 'jupyter-{username}': notebook_dir }
+
# Configuration file for jupyterhub.
+
+c = get_config()  #noqa
+c.JupyterHub.port = 8000
+c.JupyterHub.hub_bind_url = "http://0.0.0.0:8081"
+c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
+c.DockerSpawner.remove = True
+c.Spawner.http_timeout = 3600
+c.DockerSpawner.image_whitelist = {
+    'iorocker': 'eeholmes/iorocker-standalone:20231003',
+    'rocker-binder': 'eeholmes/rocker-binder:20231003',
+    'openscapes-rocker': 'eeholmes/minimal-jhub:20231004',
+    'datascience-r': 'jupyter/datascience-notebook:r-4.3.1',
+    'scipy-notebook': 'jupyter/scipy-notebook:7e1a19a8427f',
+}
+
+notebook_dir = '/home/jovyan'
+c.DockerSpawner.notebook_dir = notebook_dir
+
+# Mount the real user's Docker volume on the host to the notebook user's
+# notebook directory in the container
+c.DockerSpawner.volumes = { 'jupyter-{username}': notebook_dir }

Docker pull of the images. Do all.

-
docker pull jupyter/datascience-notebook:r-4.3.1
-docker pull jupyter/scipy-notebook:7e1a19a8427f
+
docker pull jupyter/datascience-notebook:r-4.3.1
+docker pull jupyter/scipy-notebook:7e1a19a8427f

Make a new server service

-
sudo mkdir -p $JHUBENV/etc/systemd
-cd $JHUBENV/etc/systemd
-nano jupyterhub.service
+
sudo mkdir -p $JHUBENV/etc/systemd
+cd $JHUBENV/etc/systemd
+nano jupyterhub.service

Paste this in

-
[Unit]
-Description=JupyterHub
-After=syslog.target network.target
-
-[Service]
-User=root
-Environment="PATH=/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/opt/miniconda3/envs/jupyterhub/bin"
-ExecStart=/opt/miniconda3/envs/jupyterhub/bin/jupyterhub -f /opt/miniconda3/envs/jupyterhub/etc/jupyterhub/jupyterhub_config.py
-
-[Install]
-WantedBy=multi-user.target
+
[Unit]
+Description=JupyterHub
+After=syslog.target network.target
+
+[Service]
+User=root
+Environment="PATH=/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/opt/miniconda3/envs/jupyterhub/bin"
+ExecStart=/opt/miniconda3/envs/jupyterhub/bin/jupyterhub -f /opt/miniconda3/envs/jupyterhub/etc/jupyterhub/jupyterhub_config.py
+
+[Install]
+WantedBy=multi-user.target

Make sure SELinux doesn’t block our service

-
ls -Z $JHUBENV/etc/systemd/
-sudo chcon system_u:object_r:systemd_unit_file_t:s0 $JHUBENV/etc/systemd/jupyterhub.service
-sudo find $JHUBENV/bin -type f -exec chcon system_u:object_r:bin_t:s0 {} \;
+
ls -Z $JHUBENV/etc/systemd/
+sudo chcon system_u:object_r:systemd_unit_file_t:s0 $JHUBENV/etc/systemd/jupyterhub.service
+sudo find $JHUBENV/bin -type f -exec chcon system_u:object_r:bin_t:s0 {} \;

Enable our new service

-
sudo ln -s $JHUBENV/etc/systemd/jupyterhub.service /etc/systemd/system/jupyterhub.service
-sudo systemctl daemon-reload
-sudo systemctl enable jupyterhub.service
-sudo systemctl start jupyterhub.service
+
sudo ln -s $JHUBENV/etc/systemd/jupyterhub.service /etc/systemd/system/jupyterhub.service
+sudo systemctl daemon-reload
+sudo systemctl enable jupyterhub.service
+sudo systemctl start jupyterhub.service

Done! See the long instructions if anything is not working.

Now go through the https and GitHub authentication steps if you need that.

@@ -860,6 +903,33 @@

Summary

} } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -926,13 +996,15 @@

Summary

return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -1011,10 +1083,9 @@

Summary

// clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -1024,8 +1095,17 @@

Summary

interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -1039,6 +1119,128 @@

Summary

const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -1081,6 +1283,7 @@

Summary

} div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -1106,6 +1309,32 @@

Summary

}); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1167,13 +1396,13 @@

Summary

@@ -1183,7 +1412,9 @@

Summary

- + @@ -1192,4 +1423,5 @@

Summary

+ \ No newline at end of file diff --git a/docs/Set-up-daskhub.html b/docs/posts/Set-up-daskhub.html similarity index 75% rename from docs/Set-up-daskhub.html rename to docs/posts/Set-up-daskhub.html index f7c9d9d..3899d6b 100644 --- a/docs/Set-up-daskhub.html +++ b/docs/posts/Set-up-daskhub.html @@ -2,7 +2,7 @@ - + @@ -24,33 +24,39 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -77,10 +84,10 @@ - - - @@ -91,8 +98,8 @@ +
@@ -188,21 +203,33 @@

DaskHub Set-up

+ + +
+

Requirements

+ +

See examples of full config.yaml files in the config directory in the nmfs-opensci/nmfs-jhub GitHub repo.

+
+
+

Set up on Azure

This is my notes for setting this up on Azure. Attempting to replicate the Openscapes 2i2c JupyterHub: https://github.com/2i2c-org/infrastructure/tree/master/config/clusters/openscapes

That hub is on AWS and is designed for large workshops (100+) however the NMFS OpenSci JHub is quite similar. Main difference at the moment is that I don’t have a shared drive set-up and the user persistent volume (storage) is on the same VM as the user node for their Jupyter Notebook. This means that I cannot have multiple VM sizes. Need to fix so that user can pick a larger VM for a task if needed.

+

Create your Kubernetes cluster

Log into https:\\portal.azure.com

  1. Get to the dashboard that looks similar to this.
-

+

  1. Click on the Kubernetes Services button and you should see something like this
-

+

  1. Click Create Kubernetes Cluster
@@ -222,7 +249,7 @@

Create your
  • Click “Create”.
  • Once it is done deploying, you will see this.

    -

    +

    Install DaskHub on your cluster

    @@ -230,12 +257,12 @@

    Install Da

    Connect to your cluster

    Once you have created your Kubernetes cluster, you want to go to its dashboard (by clicking on the name you gave it). You’ll see something like this (I named mine daskhub).

    -

    +

    Click on the Connect icon in the nav bar at top.

    You then see this

    -

    +

    Click on the link that says “Open Cloud Shell”.

    -

    +

    You will get to a terminal. Paste in the two commands in the previous image (the commands that show up for you that is).

    @@ -257,7 +284,7 @@

    Install das dhub dask/daskhub \ --namespace=dhub --create-namespace \ --values=dconfig.yaml -

    You will see this on successful installation (it’s long. much has been cut).

    +

    You will see this on successful installation (it’s long. much has been cut).

    Set-up your external IP address

    @@ -276,12 +303,12 @@

    Create a domain name<

    Create a DNS entry

    Let’s pretend you set up bluemountain123.live as the domain. Go to the DNS settings for your domain. Add a type A record. This will do 2 things. First this will create the subdomain that you will use to access your JupyterHub. So let’s say you create, dhub as the type A DNS entry. Then dhub.bluemountain123.live will be the url. You can have as many subdomains as you need.

    -

    +

    Test if the url is working

    http:\\dhub.bluemountain123.live would be the url using the example domain above. Test that it is working (shows a JupyterHub login) before moving on. This is what you should see:

    -

    +

    Set-up https on your JupyterHub

    @@ -316,9 +343,9 @@

    C

    This is going to be associated with your (personal) GitHub account, but you can use a team on a GitHub org that you are owner of.

    Log into GitHub and go to GitHub > Settings > Developer Settings > New Oauth Application

    Look carefully at how I filled in the boxes.

    -

    +

    Next you will see something like this

    -

    +

    You need to copy the ID and then click the create secrets button and save the secret. Save those for later.

    @@ -359,7 +386,7 @@

    Update the hub

    Test

    You should now see this and can authenticate with GitHub.

    -

    +

    @@ -418,7 +445,7 @@

    Update the hub

    Changing the VM size

    NOT WORKING YET I am stuck on creating the persistent volumes. Needed because you need the user storage somewhere if you have multiple node pools.

    -

    +

    kubectl get nodes --show-labels | grep instance-type

    beta.kubernetes.io/instance-type=Standard_D8s_v3

    @@ -572,6 +599,33 @@

    S3 access

    } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -638,13 +692,15 @@

    S3 access

    return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -723,10 +779,9 @@

    S3 access

    // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -736,8 +791,17 @@

    S3 access

    interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -751,6 +815,128 @@

    S3 access

    const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -793,6 +979,7 @@

    S3 access

    } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -818,6 +1005,32 @@

    S3 access

    }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -879,13 +1092,13 @@

    S3 access

    @@ -895,7 +1108,9 @@

    S3 access

    - + @@ -904,4 +1119,5 @@

    S3 access

    + \ No newline at end of file diff --git a/docs/Setup-Notes.html b/docs/posts/Setup-Notes.html similarity index 64% rename from docs/Setup-Notes.html rename to docs/posts/Setup-Notes.html index cbe843e..a449b7d 100644 --- a/docs/Setup-Notes.html +++ b/docs/posts/Setup-Notes.html @@ -2,7 +2,7 @@ - + @@ -23,31 +23,37 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -74,10 +81,10 @@ - - - @@ -88,8 +95,8 @@ +
    +

    Instructions for editing config

      @@ -229,6 +243,33 @@

      Instructions for editing config

      } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -295,13 +336,15 @@

      Instructions for editing config

      return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -380,10 +423,9 @@

      Instructions for editing config

      // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -393,8 +435,17 @@

      Instructions for editing config

      interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -408,6 +459,128 @@

      Instructions for editing config

      const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -450,6 +623,7 @@

      Instructions for editing config

      } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -475,6 +649,32 @@

      Instructions for editing config

      }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -540,7 +740,9 @@

      Instructions for editing config

      - + @@ -549,4 +751,5 @@

      Instructions for editing config

      + \ No newline at end of file diff --git a/docs/ci/iopython-tf/instructions.html b/docs/posts/set-up-authentication.html similarity index 50% rename from docs/ci/iopython-tf/instructions.html rename to docs/posts/set-up-authentication.html index 9990997..bfe0aac 100644 --- a/docs/ci/iopython-tf/instructions.html +++ b/docs/posts/set-up-authentication.html @@ -2,12 +2,13 @@ - + + -Eli's JupyterHub notes – instructions +Eli’s JupyterHub notes - Set up JupyterHub Authentication - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -74,10 +83,10 @@ - - - @@ -88,8 +97,8 @@ +
      +
      +
      +

      Set up JupyterHub Authentication

      +
      +
      +
      +

      Setting up Authentication in various ways

      +
      +
      -
      -

      Indian Ocean Summer Docker Images

      -

      https://hub.docker.com/repository/docker/eeholmes/iopython-tf/general

      -

      The one to use is the dated one. The main tag doesn’t seem to always be recognized as a new tag when it changes.

      -
      -
      -

      Requirements

      -

      Docker installed. For example, if doing on a Mac or PC, you need Docker Desktop. On VMs, docker will already be installed.

      -

      A DockerHub user account. The instructions are using EEH’s.

      -
      -
      -

      Add new packages

      -

      Add to Dockerfile something like

      -
      RUN pip install tensorflow
      -
      -
      -

      Rebuild and push the Docker image

      -
        -
      1. Make sure Docker app is running, not just installed. So if you are on a local computer, start up the app (open it).
      2. -
      3. Go to a terminal and cd to the directory with the Dockerfile. So to the ci directory in the nmfs-jhub repo.
      4. -
      -
      cd ci/iopython-tf
      -
        -
      1. Update the docker tag to the date.
      2. -
      -
      DOCKER_TAG="20230901"
      -
        -
      1. Build the image. . means current directory. eeholmes/iopython is the name of the repo on DockerHub. See notes below.
      2. -
      -
      docker build --platform linux/amd64 -t eeholmes/iopython-tf:${DOCKER_TAG} -t eeholmes/iopython-tf:main .
      -
        -
      1. Push the image up to DockerHub. Make sure you are logged into DockerHub in the Docker app otherwise you’ll get “access denied”. Open the Docker app and look that it shows that you are signed in.
      2. -
      -
      docker push eeholmes/iopython-tf:${DOCKER_TAG}
      -docker push eeholmes/iopython-tf:main
      -

      Notes: https://help.valohai.com/hc/en-us/articles/4421364087569-Build-your-own-Docker-image

      -
      -
      -

      Stop any running the Jupyter Lab instances

      -

      Log in. File > Hub Control > Stop my server

      -
      -
      -

      Run helm upgrade

      -

      Log into Azure portal, go to DaskHub, Connect, Cloud Shell, and run this command. Note, this assumes that eeholmes/iopython:main is still the image to use. If not, edit dconfig2.yaml (nano dconfig2.yaml) and then upgrade.

      -
      helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
      -
      -
      -

      Asides

      -
      -

      To set docker tag to latest commit

      -
      SHA7="$(git rev-parse --short HEAD)"
      -DOCKER_TAG=$SHA7
      -

      I am not doing that since this repo has lots of commits unrelated to the docker image.

      -
      -
      -

      To set up your own Docker repo

      -
        -
      1. Make an account on DockerHub. Free is fine.
      2. -
      3. Create a repo and give it a name. For example, for this project, my account is eeholmes and my repo is iopython (Indian Ocean Python) as it is specific a particular project I am working on.
      4. -
      -

      DockerHub will want to you to buy the premium account but you only need that if you are doing continuous integration, like using a GitHub Action to autobuild your image. If you are manually building, you don’t need this.

      -
      -
      -

      Why is --platform needed in the build command

      -

      You won’t see this on docker build tutorials. But if you are on a Mac with Apple chip, then you’ll build arm64 images and that’s not going to work on Ubuntu VMs. The vanilla images you see are amd64 so we want to make sure we are building for that platform. This only matters if you are on a Mac with Apple chip, but it won’t break things for unix and PC so I added to make the instructions more robust.

      -
      -
      -

      If an specific image tag is in config

      -

      The JupyterHub has a config file that specifies what images are being used. If the image is say eeholmes/iopython:hublatest, then whenever the a image with tag hublatest is pushed, the hub will use that. If on the otherhand, you config file has a specific, an unique tag that you don’t overwrite, then you’ll have to update the file in the config file on the cluster (log into Azure, go to cluster, connect to cloud shell, nano dconfig2.yaml) and upgrade the installation of the JupyterHub.

      -

      Why not eeholmes/iopython:latest? There is nothing special about latest. It is the default tag used if you don’t specify -t and : in your build call. So it is a bit too easy to accidentally update “latest” and thus update the image for you hub when you didn’t intend to do that. You just forgot to specify a tag.

      -

      To update if you are using a specific tag, like 20230615 rather than one you keep updating like hublatest or latest:

      -
      -

      Step 1

      -

      Edit the config file. Mine is called dconfig2.yaml. Yours is probably config.yaml. Name is unimportant.

      -
      nano dconfig2.yaml
      -

      Inside dconfig2.yaml is this info. This shows a fixed tag. So if I update, I need to change the 20230615 part.

      -
        singleuser:
      -    image:
      -      name: eeholmes/iopython
      -      tag: 20230615
      -

      Save the changes. In nano, it cmd-O, return, cmd-X.

      -
      -
      -

      run helm upgrade

      -
      helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
      -
      -
      -

      The helm upgrade command

      -
      helm upgrade --cleanup-on-fail --render-subchart-notes dhub dask/daskhub --namespace dhub --version=2023.1.0 --values dconfig2.yaml
      -

      A helm is what runs the commands to upgrade (and install in the beginning) our JupyterHub. dask/daskhub is point to the repo with the “helm chart” (the instructions). --value dconfig2.yaml is telling it where the config file is.

      + +
      + + + + +
      + + + +
      + + +
      +

      Requirements

        -
      • upgrade upgrade an existing installation with the values in dconfig2.yaml
      • -
      • --render-subchart-notes the dask/daskhub helm chart has subcharts (jupyterhub) and you need to render these too. Not all helm charts have this.
      • -
      • dask/daskhub the name of the repo that has the helm chart. The first time you reference this, you need to tell help about the repo by giving it the url. Read how here
      • -
      • --version=2023.1.0 version of the helm chart. Update when the helm chart (instructions for installing the jupyterhub) changes.
      • +
      • Documentation: https://z2jh.jupyter.org
      +

      I assume you have https set-up and your JuptyerHub is installed with a config.yaml file. You will need to know your upgrade command when you make changes to the config.yaml file. For example, but yours will look different.

      +
      helm upgrade --cleanup-on-fail   --install jhub1 jupyterhub/jupyterhub   --namespace jhubk8   --create-namespace   --version=3.3.4   --values config.yaml
      +

      See examples of full config.yaml files in the config directory in the nmfs-opensci/nmfs-jhub GitHub repo.

      +
      +
      +

      Set up GitHub authentication

      +

      I am going to show an example where I use a team on a GitHub organization to manage authentication. There are many other ways to manage users. See the JupyterHub documentation.

      +
      +

      Create a new Oauth Application on GitHub

      +

      This is going to be associated with your (personal) GitHub account, but you can use a team on a GitHub org that you are owner of.

      +

      Log into GitHub and go to GitHub > Settings > Developer Settings > New Oauth Application

      +

      Look carefully at how I filled in the boxes.

      +

      +

      Next you will see something like this

      +

      +

      You need to copy the ID and then click the create secrets button and save the secret. Save those for later.

      +
      +

      Create a team in your GitHub org

      +

      You will be added by default and add anyone else who needs access to the hub. Let’s say your org is MyOrg and the team is called DaskHub. So then the allowed organization is MyOrg:DaskHub. You can leave off :DaskHub if you want to allow all members of the organization to log in.

      -
      -

      Adding packages with newpackages.yml

      -

      When the openscapes image is used, we are in a conda env called ‘notebook’. We want to update that with the packages in newpackages.yml but need to get that file into the container. For now, I just hard code in the pip install commands.

      -

      Add to Docker file

      -
      # it can't find new.yml in home/joyvan/.kernels
      -# need to get that into the container somehow (git clone?)
      -# RUN conda env update --file new.yml
      +
      +

      Edit the config.yaml file

      +
      nano config.yaml
      +

      Add the following to your config file (nano config.yaml). Replace the id, secret and url with your values. We need to set the KubeSpawner working directory because the Openscapes Docker image sets it to home/jovyan/.kernels–which is fine but annoying since .kernels is hidden and not $HOME.

      +

      Make sure you have “myteam” created as a team in your GitHub org (“myorg”) and that you have added yourself to that team.

      +
      hub:
      +  config:
      +    GitHubOAuthenticator:
      +      client_id: <replace by what is on GitHub>
      +      client_secret: <replace with secret on GitHub>
      +      oauth_callback_url: https://dhub.bluemountain123.live/hub/oauth_callback
      +      allowed_organizations:
      +        - myorg:myteam
      +      scope:
      +        - read:org
      +    Authenticator:
      +      admin_users:
      +        - yourGitHubusername
      +    JupyterHub:
      +      authenticator_class: github
      +    KubeSpawner:
      +      working_dir: /home/jovyan
      +

      Update the hub.

      +
      helm upgrade --cleanup-on-fail   --install jhub1 jupyterhub/jupyterhub   --namespace jhubk8   --create-namespace   --version=3.3.4   --values config.yaml
      +

      Test. Go to https:\\dhub.bluemountain123.live and it should ask you to login with GitHub. There will be some popups asking permission for the OAuth to have access to “myorg”. Make sure you grant access to “myorg”. Oh no, I forgot! You’ll probably have to go through the process of recreating an OAuth. I am not sure how to trigger it to ask to grant access again.

      +
      +
      +

      If you use daskhub helm chart

      +

      If you used the daskhub helm chart instead of the jupyterhub helm chart, then the config.yaml file has a jupyterhub: section:

      +
      jupyterhub:
      +  hub:
      +    config:
      +      GitHubOAuthenticator:
      +        client_id: xxxxxxx
      +        client_secret: xxxxxxx
      +        oauth_callback_url: https://dhub.opensci.live/hub/oauth_callback
      +        allowed_organizations:
      +          - myorg:myteam
      +        scope:
      +          - read:org
      +      Authenticator:
      +        admin_users:
      +          - eeholmes
      +      JupyterHub:
      +        authenticator_class: github
      +      KubeSpawner:
      +        working_dir: /home/jovyan
      @@ -324,6 +317,33 @@

      Addin } } } + const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => { + const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light'; + const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark'; + let newTheme = ''; + if(darkModeDefault) { + newTheme = isAlternate ? baseTheme : alternateTheme; + } else { + newTheme = isAlternate ? alternateTheme : baseTheme; + } + const changeGiscusTheme = () => { + // From: https://github.com/giscus/giscus/issues/336 + const sendMessage = (message) => { + const iframe = document.querySelector('iframe.giscus-frame'); + if (!iframe) return; + iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app'); + } + sendMessage({ + setConfig: { + theme: newTheme + } + }); + } + const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null; + if (isGiscussLoaded) { + changeGiscusTheme(); + } + } const toggleColorMode = (alternate) => { // Switch the stylesheets const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate'); @@ -390,13 +410,15 @@

      Addin return localAlternateSentinel; } } - let localAlternateSentinel = 'default'; + const darkModeDefault = false; + let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default'; // Dark / light mode switch window.quartoToggleColorScheme = () => { // Read the current dark / light value let toAlternate = !hasAlternateSentinel(); toggleColorMode(toAlternate); setStyleSentinel(toAlternate); + toggleGiscusIfUsed(toAlternate, darkModeDefault); }; // Ensure there is a toggle, if there isn't float one in the top right if (window.document.querySelector('.quarto-color-scheme-toggle') === null) { @@ -475,10 +497,9 @@

      Addin // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { const config = { allowHTML: true, - content: contentFn, maxWidth: 500, delay: 100, arrow: false, @@ -488,8 +509,17 @@

      Addin interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -503,6 +533,128 @@

      Addin const note = window.document.getElementById(id); return note.innerHTML; }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + const typesetMath = (el) => { + if (window.MathJax) { + // MathJax Typeset + window.MathJax.typeset([el]); + } else if (window.katex) { + // KaTeX Render + var mathElements = el.getElementsByClassName("math"); + var macros = []; + for (var i = 0; i < mathElements.length; i++) { + var texText = mathElements[i].firstChild; + if (mathElements[i].tagName == "SPAN") { + window.katex.render(texText.data, mathElements[i], { + displayMode: mathElements[i].classList.contains('display'), + throwOnError: false, + macros: macros, + fleqn: false + }); + } + } + } + } + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + for (let i = 0; i < 2; i++) { + container.appendChild(note.children[i].cloneNode(true)); + } + typesetMath(container); + return container.innerHTML + } else { + typesetMath(note); + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + typesetMath(note); + return note.innerHTML; + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); } let selectedAnnoteEl; const selectorForAnnotation = ( cell, annotation) => { @@ -545,6 +697,7 @@

      Addin } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -570,6 +723,32 @@

      Addin }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -629,13 +808,24 @@

      Addin } }); +