From fe8551df1eadd8cf3cc46d2aa0d77d194abecaca Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Fri, 18 Sep 2026 16:47:04 +0100 Subject: [PATCH 1/4] Create Dockerfile for BioSim workshop setup Set up Dockerfile for BioSim workshop environment with necessary dependencies. --- docker/Dockerfile | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docker/Dockerfile diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..a60069c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,28 @@ +# Start with BioSim base image. +ARG BASE_IMAGE=latest@sha256:5eb67b09e3fdf64b3128bd9501e08a0cbb007804b678f2096728d8c1260e6217 +FROM ghcr.io/ccpbiosim/jupyterhub-base:$BASE_IMAGE + +LABEL maintainer="James Gebbie-Rayet " + +# Switch to jovyan user. +USER $NB_USER +WORKDIR $HOME + +# Install workshop deps +RUN conda install -y -c conda-forge \ + conda-forge::openfe=1.12 \ + conda-forge::nglview=4.0.1 \ + conda-forge::ipywidgets=8.1.9 + +COPY docker/fix-nglview.sh /tmp/fix-nglview.sh +RUN bash /tmp/fix-nglview.sh + +# Get workshop files and move them to jovyan directory. +COPY --chown=1000:100 . . +RUN rm -rf LICENSE README.md docker .git .github environment.yml + +# Copy lab workspace +#COPY --chown=1000:100 docker/default-37a8.jupyterlab-workspace /home/jovyan/.jupyter/lab/workspaces/default-37a8.jupyterlab-workspace + +# Always finish with non-root user as a precaution. +USER $NB_USER From 999806b6e350ec2be968362a8064682f992fb93e Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Fri, 18 Sep 2026 16:48:35 +0100 Subject: [PATCH 2/4] Add Renovate configuration for dependency management --- .github/renovate.json | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/renovate.json diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..0b3e5d8 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:best-practices", + "helpers:pinGitHubActionDigestsToSemver" + ], + "timezone": "Europe/London", + "schedule": ["after 5am and before 5pm every weekday"], + "dependencyDashboard": true, + "assignees": ["jimboid"], + "minimumReleaseAge": "3 days", + "packageRules": [ + { + "matchManagers": ["github-actions"], + "groupName": "GitHub Actions", + "addLabels": ["ci", "dependencies"] + } + ], + "customManagers": [ + { + "customType": "regex", + "managerFilePatterns": [ + "/(^|/)Dockerfile[^/]*$/" + ], + "matchStringsStrategy": "recursive", + "matchStrings": [ + "pip3? install[\\s\\S]*?(?(?:[\\w.-]+==[\\w.-]+[\\s\\\\]*)+)", + "(?[\\w.-]+)==(?[\\w.-]+)" + ], + "datasourceTemplate": "pypi", + "versioningTemplate": "pep440" + }, + { + "customType": "regex", + "managerFilePatterns": [ + "/(^|/)Dockerfile[^/]*$/" + ], + "matchStringsStrategy": "recursive", + "matchStrings": [ + "(?:mamba|conda) install[\\s\\S]*?(?(?:(?:[\\w-]+::)?[\\w.-]+=[\\w.-]+[\\s\\\\]*)+)", + "(?:(?[\\w-]+)::)?(?[\\w.-]+)=(?[\\w.-]+)" + ], + "datasourceTemplate": "conda", + "registryUrlTemplate": "https://api.anaconda.org/package/{{#if channel}}{{channel}}{{else}}conda-forge{{/if}}/", + "versioningTemplate": "pep440" + } + ] +} From 0a275e30f3719084e4e4ebc2ea653db4e4581acd Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Fri, 18 Sep 2026 16:50:18 +0100 Subject: [PATCH 3/4] Disable test runs and version updates in build workflow Comment out test execution steps for pull requests and version updates. --- .github/workflows/build.yaml | 179 +++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 .github/workflows/build.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..491c927 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,179 @@ +name: ci/cd +on: + pull_request: + repository_dispatch: + types: [build] + workflow_dispatch: + +jobs: + build: + timeout-minutes: 360 + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-24.04' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }} + name: build ${{ matrix.platform }} + outputs: + tag: ${{ steps.envvars.outputs.tag }} + steps: + - name: "node-cleanup" + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL + sudo docker image prune --all --force + sudo docker builder prune -a + + - name: checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Prepare env + id: envvars + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + if [ ${{ github.event.client_payload.tag }} != 'null' ]; then + echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + else + echo "tag=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + fi + + - name: Metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + + - name: Build and push by digest + if: github.event_name == 'pull_request' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + file: ./docker/Dockerfile + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + tags: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }} + outputs: type=docker,push-by-digest=true,name-canonical=true,push=true + +# - name: Run tests +# if: github.event_name == 'pull_request' +# shell: bash +# run: | +# docker run -t ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:latest bash -c " \ +# pip install pytest nbmake; \ +# find . -name '*.ipynb' | pytest --nbmake --nbmake-timeout=3600;" + + - name: Authenticate with GHCR + if: github.event_name != 'pull_request' + id: auth + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.BUILD_TOKEN }} + + - name: Build and push by digest + if: github.event_name != 'pull_request' + id: build + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + file: ./docker/Dockerfile + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + tags: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + +# - name: Run tests +# if: github.event_name != 'pull_request' +# shell: bash +# run: | +# docker run -t ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}@${{ steps.build.outputs.digest }} bash -c " \ +# pip install pytest nbmake; \ +# find . -name '*.ipynb' | pytest --nbmake --nbmake-timeout=3600;" + + - name: Export digest + if: github.event_name != 'pull_request' + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + tags: + if: github.event_name != 'pull_request' + runs-on: ubuntu-24.04 + name: merge and tag + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Authenticate with GHCR + id: auth + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.BUILD_TOKEN }} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + + - name: Metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }} + tags: | + latest + ${{ needs.build.outputs.tag }} + + - name: Create manifest list and push + id: annotate + continue-on-error: true + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + --annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \ + --annotation='index:org.opencontainers.image.licenses=MIT' \ + --annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \ + --annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \ + --annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \ + $(printf 'ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}@sha256:%s ' *) + + - name: Create manifest list and push without annotations + if: steps.annotate.outcome == 'failure' + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf 'ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ghcr.io/${{ vars.ORG_REPO }}/${{ github.event.repository.name }}:latest + +# - name: Post version update to dash +# uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 +# with: +# token: ${{ secrets.BUILD_TOKEN }} +# repository: ccpbiosim/ccpbiosim.github.io +# event-type: build +# client-payload: '{"repo": "${{ github.event.repository.name }}", "tag": "${{ needs.build.outputs.tag }}"}' From 633427858e18afc1bfb34b2aaf543856c1e5d4f8 Mon Sep 17 00:00:00 2001 From: James Gebbie-Rayet Date: Fri, 18 Sep 2026 16:54:28 +0100 Subject: [PATCH 4/4] Add script to fix nglview version mismatches This script checks for version mismatches between nglview's Python and JavaScript components and patches the Python frontend file if necessary. --- docker/fix-nglview.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 docker/fix-nglview.sh diff --git a/docker/fix-nglview.sh b/docker/fix-nglview.sh new file mode 100644 index 0000000..9fdd518 --- /dev/null +++ b/docker/fix-nglview.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Locate nglview's Python frontend file +NGLVIEW_DIR=$(python -c "import nglview, os; print(os.path.dirname(nglview.__file__))") +FRONTEND_FILE="$NGLVIEW_DIR/_frontend.py" + +if [[ ! -f "$FRONTEND_FILE" ]]; then + echo "nglview _frontend.py not found, skipping patch" >&2 + exit 0 +fi + +# Locate the installed labextension's package.json by searching +# every labextensions directory Jupyter knows about (handles conda's +# $CONDA_PREFIX/share/jupyter/labextensions layout automatically) +LABEXT_PKG=$(python -c " +from jupyter_core.paths import jupyter_path +import os, sys +for d in jupyter_path('labextensions'): + candidate = os.path.join(d, 'nglview-js-widgets', 'package.json') + if os.path.isfile(candidate): + print(candidate) + sys.exit(0) +sys.exit(1) +") || { echo "nglview-js-widgets labextension not found, skipping patch" >&2; exit 0; } + +PY_VERSION=$(python -c "import re; print(re.search(r\"__frontend_version__ = '([^']+)'\", open('$FRONTEND_FILE').read()).group(1))") +JS_VERSION=$(python -c "import json; print(json.load(open('$LABEXT_PKG'))['version'])") + +# If there is a version mismatch then make them the same python side. +if [[ "$PY_VERSION" != "$JS_VERSION" ]]; then + echo "nglview mismatch: python=$PY_VERSION js=$JS_VERSION (found at $LABEXT_PKG) -- patching" + sed -i "s/__frontend_version__ = '$PY_VERSION'/__frontend_version__ = '$JS_VERSION'/" "$FRONTEND_FILE" +else + echo "nglview versions already match ($PY_VERSION), skipping patch" +fi