From 1db4c76733cd5a2179d8ced1c922619ff43089fc Mon Sep 17 00:00:00 2001 From: Abdelsalam Date: Sat, 2 May 2026 15:23:18 +0300 Subject: [PATCH 1/8] Add notebook image CI build (mdx2:1.0.2-1) Reproduces diffuseproject/mdx2:test using a captured conda lockfile + mdx2 pinned to 327bf6e (PR #56 head, the source :test was built from), then adds openssh-client, openssh-sftp-server, and rsync so scp, sftp, and rsync work through the SSH gateway. The existing :1.0.0/:latest build job is left untouched. --- .github/workflows/docker.yml | 29 +++ Dockerfile.notebook | 75 +++++++ notebook-env.lock | 389 +++++++++++++++++++++++++++++++++++ 3 files changed, 493 insertions(+) create mode 100644 Dockerfile.notebook create mode 100644 notebook-env.lock diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2b86bf8..f6e730e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -40,3 +40,32 @@ jobs: tags: ${{ steps.tags.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max + + notebook: + name: notebook image (singleuser) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build (and push on main) + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile.notebook + build-args: | + MDX2_COMMIT=327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 + push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + tags: diffuseproject/mdx2:1.0.2-1 + cache-from: type=gha,scope=notebook + cache-to: type=gha,mode=max,scope=notebook diff --git a/Dockerfile.notebook b/Dockerfile.notebook new file mode 100644 index 0000000..9951cf4 --- /dev/null +++ b/Dockerfile.notebook @@ -0,0 +1,75 @@ +# Notebook image for the Diffuse JupyterHub deployment (singleuser pods). +# Reproduces diffuseproject/mdx2:test using a captured conda lockfile +# (notebook-env.lock) for byte-equivalent scientific stack parity with prod, +# plus openssh-client / openssh-sftp-server / rsync so scp/sftp/rsync work +# through the SSH gateway. +# +# Build: +# docker buildx build \ +# --build-arg MDX2_COMMIT=327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 \ +# -f Dockerfile.notebook -t diffuseproject/mdx2:1.0.2-1 . +# +# 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 is the HEAD of feat/jupyterhub-singleuser +# (PR #56 source ref) on 2026-02-25, which is what diffuseproject/mdx2:test was +# built from. mdx2/VERSION=1.0.2. Always use the full 40-char SHA — Docker's +# `git clone` defaults to a shallow clone that may not resolve short SHAs. + +FROM python:3.10-slim AS python_stage + +FROM mambaorg/micromamba:1.5.5 AS micromamba_stage + +# Isolated stage: clone mdx2 at the pinned commit. Keeps git out of the final image. +# +# MDX2_COMMIT is on a deleted branch (`feat/jupyterhub-singleuser`, PR #56) and +# is only reachable via the PR head ref `refs/pull/56/head`. We fetch that ref +# explicitly because default `git clone` doesn't fetch refs/pull/* refs. +# TODO: ask diff-use/mdx2 maintainers to create a durable tag for this commit +# (e.g. `singleuser-source-pin`) so we can drop the PR-ref dependency. +FROM debian:stable-slim AS source_stage +ARG MDX2_COMMIT +ARG MDX2_PR_REF=pull/56/head +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates git \ + && rm -rf /var/lib/apt/lists/* +RUN git clone https://github.com/diff-use/mdx2.git /tmp/mdx2 \ + && cd /tmp/mdx2 \ + && git fetch origin "${MDX2_PR_REF}:refs/heads/source-pin" \ + && git checkout "${MDX2_COMMIT}" + +FROM debian:stable-slim AS final + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + openssh-client \ + openssh-sftp-server \ + rsync \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=python_stage /usr/local /usr/local +COPY --from=micromamba_stage /bin/micromamba /usr/local/bin/micromamba + +ENV MAMBA_ROOT_PREFIX="/root/micromamba" \ + CONDA_PREFIX="/root/micromamba/envs/mdx2-dev" \ + PATH="/root/micromamba/envs/mdx2-dev/bin:/usr/local/bin:/opt/conda/bin:$PATH" + +RUN mkdir -p /opt/conda + +WORKDIR /home/dev + +# Conda env from explicit lockfile — no solve, deterministic, byte-equivalent to live :test pod. +COPY notebook-env.lock /home/dev/notebook-env.lock +RUN --mount=type=cache,target=/root/micromamba/pkgs \ + /usr/local/bin/micromamba create -n mdx2-dev --yes --file /home/dev/notebook-env.lock + +COPY --from=source_stage /tmp/mdx2/ /home/dev/ + +RUN --mount=type=cache,target=/root/.cache/pip \ + /usr/local/bin/micromamba run -n mdx2-dev pip install -e . \ + && /usr/local/bin/micromamba run -n mdx2-dev pip install \ + jupyterhub==5.4.3 \ + jupyter-vscode-proxy==0.7 + +EXPOSE 8888 + +CMD ["/usr/local/bin/micromamba", "run", "-n", "mdx2-dev", "jupyterhub-singleuser"] diff --git a/notebook-env.lock b/notebook-env.lock new file mode 100644 index 0000000..55d4766 --- /dev/null +++ b/notebook-env.lock @@ -0,0 +1,389 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: linux-64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda#239c5e9546c38a1e884d69effcf4c882 +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda#a9f577daf3de00bca7c3c76c0ecbd1de +https://conda.anaconda.org/conda-forge/linux-64/hicolor-icon-theme-0.17-ha770c72_3.conda#129e404c5b001f3ef5581316971e3ea0 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda#0aa00f03f9e39fb9876085dee11a85d4 +https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda#e7f7ce06ec24cfcfb9e36d28cf82ba57 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda#edb0dca6bc32e4f4789199455a1dbeb8 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda#5f13ffc7d30ffec87864e678df9957b4 +https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.1-h73754d4_0.conda#8e7251989bca326a28f4a5ffbd74557a +https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda#f4084e4e6577797150f9b04a4560ceb0 +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda#db409b7c1720428638e7c0d509d3e1b5 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.17.1-h27c8c51_0.conda#867127763fbe935bab59815b6e0b7b5c +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb +https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda#49023d73832ef61042f6a237cb2687e7 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda#a7970cd949a077b7cb9696379d338681 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda#1b08cd684f34175e4514474793d44bcb +https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda#186a18e3ba246eccfc7cff00cd19a870 +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda#a360c33a5abe61c07959e449fa1453eb +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda#915f5995e94f60e9a4826e0b0920ee88 +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda#d2ffd7602c02f2b316fd921d39876885 +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda#7a3bff861a6583f1889021facefc08b1 +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.4-h6548e54_1.conda#bb26456332b07f68bf3b7622ed71c0da +https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda#b3c17d95b5a10c6e64a21fa17573e70e +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda#b2895afaf55bf96a8c8282a2e47a5de0 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda#1dafce8548e38671bea82e3f5c6ce22f +https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda#92ed62436b625154323d40d5f2f11dd7 +https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda#c01af13bdc553d1a8fbfff6e8db075f0 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda#fb901ff28063514abb6046c9ec2c4a45 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda#1c74ff8c35dcadf952a16f752ca5aa49 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda#861fb6ccbc677bb9a9fb2468430b9c6a +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda#34e54f03dfea3e7a2dcf1453a85f1085 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda#96d57aba173e878a2089d5638016dc5e +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda#bb6c4808bfa69d6f7f6b07e5846ced37 +https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda#8397539e3a0bbd1695584fb4f927485a +https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda#c7c83eecbb72d88b940c249af56c8b17 +https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda#9344155d33912347b37f0ae6c410a835 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda#6c77a605a7a689d17d4819c0f8ac9a00 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda#aea31d2e5b1091feca96fcfe945c3cf9 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda#4a13eeac0b5c8e5b8ab496e6c4ddd829 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda#cd5a90476766d53e901500df9215e927 +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.44.5-h2b0a6b4_1.conda#7eb4977dd6f60b3aaab0715a0ea76f11 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-hca6bf5a_1.conda#3fdd8d99683da9fe279c2f4cecd1e048 +https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.16-hb03c661_0.conda#f9f81ea472684d75b9dd8d0b328cf655 +https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda#2cd94587f3a401ae05e03a6caf09539d +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.3.2-h6083320_0.conda#d170a70fc1d5c605fcebdf16851bd54a +https://conda.anaconda.org/conda-forge/linux-64/pango-1.56.4-hadf4263_0.conda#79f71230c069a287efe3a8614069ddf1 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.60.2-h61e6d4b_0.conda#d62da3d560992bfa2feb611d7be813b8 +https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda#b3f0179590f3c0637b7eb5309898f79e +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.15.3-hb03c661_0.conda#dcdc58c15961dbf17a0621312b01f5cb +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda#12bd9a3f089ee6c9266a37dab82afabd +https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda#d864d34357c3b65a4b731f78c0801dc4 +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.2-hf4e2dac_0.conda#da5be73701eecd0e8454423fd6ffcf30 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda#d5e96b1ed75ca01906b3d2469b4ce493 +https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda#47e340acb35de30501a76c7c799c41d7 +https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda#4492fd26db29495f0ba23f146cd5638d +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda#f61eb8cd60ff9057122a3d338b99c00f +https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda#d7d95fc8287ea7bf33e0e7116d2b95ec +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda#cffd3bdd58090148f4cfcd831f4b26ab +https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda#ad659d0a2b3e47e38d829aa8cad2d610 +https://conda.anaconda.org/conda-forge/linux-64/python-3.10.19-h3c07f61_3_cpython.conda#be48679ccfbc8710dea1d5970600fa04 +https://conda.anaconda.org/conda-forge/noarch/ansi2html-1.9.2-pyhcf101f3_3.conda#d8573ef104c250314148c020f4c552e4 +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda#0caa1af407ecff61170c9437a808404d +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda#8e662bd460bda79b1ea39194e3c4c9ab +https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda#53abe63df7e10a6ba605dc5f9f961d36 +https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.1-pyhcf101f3_0.conda#11a2b8c732d215d977998ccd69a9d5e8 +https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda#12c566707c80111f9799308d9e265aef +https://conda.anaconda.org/conda-forge/noarch/python_abi-3.10-8_cp310.conda#05e00f3b21e88bb3d658ac700b2ce58c +https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py310he7384ee_1.conda#803e2d778b8dcccdc014127ec5001681 +https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py310h7c4b9e2_2.conda#7f9a178be0c687e77f7248507737d15e +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda#edd329d7d3a4ab45dcf905899a7a6115 +https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda#8ac12aff0860280ee0cff7fa2cf63f3b +https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda#3339e3b65d58accf4ca4fb8748ab16b3 +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda#5b8d21249ff20967101ffa321cab24e8 +https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.3-pyhd8ed1ab_0.conda#7ead57407430ba33f681738905278d03 +https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda#85c4f19f377424eafc4ed7911b291642 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda#646855f357199a12f02a87382d429b75 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda#9063115da5bc35fdc3e1002e69b9ef6e +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda#be43915efc66345cccb3c310b6ed0374 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda#c160954f7418d7b6e87eaf05a8913fa9 +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda#6636a2b6f1a87572df2970d3ebc87cc0 +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda#b38076eb5c8e40d0106beda6f95d7609 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda#6235adb93d064ecdf3d44faee6f468de +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py310hb13e2d6_0.conda#6593de64c935768b6bad3e19b3e978be +https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.0-pyh332efcf_0.conda#1d00d46c634177fc8ede8b99d6089239 +https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda#b76541e68fea4d511b1ac46a28dcd2c6 +https://conda.anaconda.org/conda-forge/noarch/wheel-0.46.3-pyhd8ed1ab_0.conda#bdbd7385b4a67025ac2dba4ef8cb6a8f +https://conda.anaconda.org/conda-forge/noarch/pip-26.0.1-pyh8b19718_0.conda#67bdec43082fd8a9cffb9484420b39a2 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda#30cd29cb87d819caead4d55184c1d115 +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda#63ccfdc3a3ce25b027b8767eb722fca8 +https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda#72e780e9aa2d0a3295f59b1874e3768b +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-9.2.2-pyhd8ed1ab_0.conda#e2e4d7094d0580ccd62e2a41947444f3 +https://conda.anaconda.org/conda-forge/noarch/asteval-1.0.8-pyhd8ed1ab_0.conda#361b12fb5a595f025ab0289c715a56bd +https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda#9673a61a297b00016442e022d689faa6 +https://conda.anaconda.org/conda-forge/noarch/async-lru-2.2.0-pyhcf101f3_0.conda#2cdaf7f8bda7eb9ce49c3e08f2f65803 +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda#ce96f2f470d39bd96ce03945af92e280 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda#ba231da7fccf9ea1e768caf5c7099b84 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda#17dcc85db3c7886650b8908b183d6876 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda#7bbe9a0cc0df0ac5f5a8ad6d6a11af2f +https://conda.anaconda.org/conda-forge/linux-64/at-spi2-core-2.40.3-h0630a04_0.tar.bz2#8cb2fc4cd6cc63f1369cfa318f581cc3 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-h04ea711_2.conda#f730d54ba9cd543666d7220c9f7ed563 +https://conda.anaconda.org/conda-forge/linux-64/at-spi2-atk-2.38.0-h0630a04_3.tar.bz2#6b889f174df1e0f816276ae69281af4d +https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-h39aace5_0.conda#791365c5f65975051e4e017b5da3abf5 +https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda#537296d57ea995666c68c821b00e360b +https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda#bc8e3267d44011051f2eb14d22fb0960 +https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_0.conda#ea5be9abc2939c8431893b4e123a2065 +https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py310h69bd2ac_0.conda#276a3ddf300498921601822e3b407088 +https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda#18de09b20462742fe093ba39185d9bac +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda#5267bef8efea4127aacd1f4e1f149b6e +https://conda.anaconda.org/conda-forge/linux-64/biopython-1.86-py310h7c4b9e2_1.conda#c510e0d5fc98881db295dc93264dd1bf +https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda#2841eb5bfc75ce15e9a0054b98dcd64d +https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda#7c5ebdc286220e8021bf55e6384acd67 +https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda#f1acf5fdefa8300de697982bcb1761c9 +https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda#f11a319b9700b203aa14c295858782b6 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda#72c8fd1af66bd67bf580645b426513ed +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda#366b40a69f0ad6072561c1d09301c886 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda#4ffbb341c8b616aa2494b6afb26a0c5f +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda#af39b9a8711d4a8d437b52c1d78eb6a1 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda#8ccf913aaba749a5496c17629d859ed1 +https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py310hba01987_1.conda#393fca4557fbd2c4d995dcb89f569048 +https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda#920bb03579f15389b9e512095ad995b7 +https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2#576d629e47797577ab0f1b351297ef4a +https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2#9b347a7ec10940d3f7941ff6c460b551 +https://conda.anaconda.org/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_2.conda#1054c53c95d85e35b88143a3eda66373 +https://conda.anaconda.org/conda-forge/linux-64/libboost-1.86.0-hd24cca6_5.conda#0d46f3db184ec649d41c3dfbfe986742 +https://conda.anaconda.org/conda-forge/linux-64/libboost-python-1.86.0-py310hc563356_5.conda#89e28e4ca1d96cf4dfe9e0ce85a7299e +https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda#434ca7e50e40f4918ab701e3facd59a0 +https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_2.conda#7df50d44d4a14d6c31a2c54f2cd92157 +https://conda.anaconda.org/conda-forge/linux-64/libglu-9.0.3-h5888daf_1.conda#8422fcc9e5e172c91e99aef703b3ce65 +https://conda.anaconda.org/conda-forge/linux-64/libsvm-337-hecca717_0.conda#50d5c24f5e2f4e79510e0632a0cd76c8 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.2-py310h3788b33_0.conda#b6420d29123c7c823de168f49ccdfe6a +https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda#4c2a8fef270f6c69591889b93f9f55c1 +https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda#37293a85a0f4f77bbd9cf7aaefc62609 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py310h7c4b9e2_0.conda#234e9858dd691d3f597147e22cbf16cf +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.61.1-py310h3406613_0.conda#24fa891e40acdb1c7f51efd0c5f97084 +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.1-ha770c72_0.conda#4afc585cd97ba8a23809406cd8a9eda8 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.9-py310haaf941d_2.conda#7426d76535fc6347f1b74f85fb17d6eb +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda#6f2e2c8f58160147c4d1c6f4c14cbac4 +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda#11b3379b191f63139e29c0d19dee24cd +https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda#2aadb0d17215603a82a2a6b0afd9a4cb +https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.1-py310h5a73078_0.conda#5e79faa7f34986b6124525f3d234512e +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda#3687cc0b82a8b4c17e1f0eb7e47163d5 +https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda#353823361b1d27eb3960efb076dfcaf6 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py310hfde16b3_0.conda#093b60a14d2c0d8c10f17e14a73a60d3 +https://conda.anaconda.org/conda-forge/noarch/mrcfile-1.5.4-pyhd8ed1ab_0.conda#f7c1396aa66942e77cf544ba6945111d +https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py310h139afa4_0.conda#d210342acdb8e3ca6434295497c10b7c +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda#a22d1fd9bf98827e280a02875d9a007a +https://conda.anaconda.org/conda-forge/noarch/freetype-py-2.3.0-pyhd8ed1ab_0.tar.bz2#e4a165cdbbaed5bbb6e653b823156151 +https://conda.anaconda.org/conda-forge/linux-64/pycairo-1.29.0-py310h8c3e0f7_1.conda#3c745bc64fa081b67605263e47b80a16 +https://conda.anaconda.org/conda-forge/noarch/rlpycairo-0.4.0-pyh6c17108_0.conda#cc70086eaf08be7f62fd44842c013916 +https://conda.anaconda.org/conda-forge/noarch/reportlab-4.4.10-pyhcf101f3_1.conda#c89e6c0a10804fb87536a3980685990d +https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda#765c4d97e877cdbbb88ff33152b86125 +https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac +https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda#164fc43f0b53b6e3a7bc7dce5e4f1dc9 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda#461219d1a5bd61342293efa2c0c90eac +https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda#9272daa869e03efe68833e3dc7a02130 +https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhcf101f3_1.conda#c65df89a0b2e321045a9e01d1337b182 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py310h1d65ade_0.conda#8c29cd33b64b2eb78597fa28b5595c8d +https://conda.anaconda.org/conda-forge/linux-64/cctbx-base-2025.12-py310h58bac43_0.conda#b6efe28a462358b86698e95433ebac8c +https://conda.anaconda.org/conda-forge/noarch/colored-2.3.1-pyhd8ed1ab_0.conda#e2e6361f6abfb9a6fd00d20ebe07b339 +https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda#2da13f2b299d8e1995bafbbe9689a2f7 +https://conda.anaconda.org/conda-forge/noarch/cuda-version-13.1-h2ff5cdb_3.conda#d85448460c25ee43ff2f8346bb9ad52b +https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-13.1.80-h376f20c_0.conda#4dc4c3a1e010e06035f01d661c1b70bd +https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-13.1.80-hecca717_0.conda#2e2b71c8d67f6ceb1d3820aa438f3580 +https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-13.1.115-hecca717_0.conda#df16c9049d882cdaf4f83a5b90079589 +https://conda.anaconda.org/conda-forge/linux-64/opencl-headers-2025.06.13-h5888daf_0.conda#45c3d2c224002d6d0d7769142b29f986 +https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.3-hb9d3cd8_0.conda#56f8947aa9d5cf37b0b3d43b83f34192 +https://conda.anaconda.org/conda-forge/linux-64/cuda-opencl-13.1.115-hecca717_0.conda#d7a0f3daf84d493616a311420cc6ab02 +https://conda.anaconda.org/conda-forge/linux-64/libcublas-13.2.1.1-h676940d_0.conda#f904a04f3e173de15d3c31bd3dfc21c7 +https://conda.anaconda.org/conda-forge/linux-64/libcufft-12.1.0.78-hecca717_0.conda#58a7aa38206ea03a9eb6ccbcc012901e +https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda#db63358239cbe1ff86242406d440e44a +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.77-h3ff7636_0.conda#09c264d40c67b82b49a3f3b89037bd2e +https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-259.1-h6569c3e_0.conda#dbd34a7b836b479604b8b5f07c3a35ba +https://conda.anaconda.org/conda-forge/linux-64/libudev1-259.1-h6569c3e_0.conda#8b11ee89763fc236d854c46cab7e762d +https://conda.anaconda.org/conda-forge/linux-64/rdma-core-61.0-h192683f_0.conda#d487d93d170e332ab39803e05912a762 +https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.16.1.26-hd07211c_0.conda#48418c48dac04671fa46cb446122b8a5 +https://conda.anaconda.org/conda-forge/linux-64/libnuma-2.0.18-hb9d3cd8_3.conda#20ab6b90150325f1af7ca96bffafde63 +https://conda.anaconda.org/conda-forge/linux-64/libcuobjclient-1.0.0.26-hecca717_0.conda#658e504357d69bb0c5c1523500fa7526 +https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.4.1.81-h676940d_0.conda#5926fbc6df184a110130a310608cb5e8 +https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-13.1.115-hecca717_1.conda#851acc1af02d31c732b931b9ffddc2d9 +https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.7.3.1-hecca717_0.conda#915b747d67493ba94a0d9b79095cc06d +https://conda.anaconda.org/conda-forge/linux-64/libcusolver-12.0.9.81-h676940d_0.conda#17a342e69a0821ecf76a0e79a2044288 +https://conda.anaconda.org/conda-forge/linux-64/libnpp-13.0.3.3-h676940d_0.conda#ebf107c5a5873fd5c634f4b2208454b0 +https://conda.anaconda.org/conda-forge/linux-64/libnvfatbin-13.1.115-hecca717_0.conda#5d5c68679881ec4dc322f2b6f19655a4 +https://conda.anaconda.org/conda-forge/linux-64/libnvjpeg-13.0.3.75-hecca717_0.conda#180135da1ae308eea56eaa9f9aff1894 +https://conda.anaconda.org/conda-forge/linux-64/cuda-libraries-13.1.1-ha770c72_1.conda#ad2af2e603c4fb4bf4a558c81658da07 +https://conda.anaconda.org/conda-forge/noarch/cuda-runtime-13.1.1-ha804496_0.conda#207e31ab65b030e71485175dbc930e6b +https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda#b38117a3c920364aff79f870c984b4a3 +https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda#c277e0a4d549b03ac1e9d6cbbe3d017b +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda#3f43953b7d3fb3aaa1d0d0723d91e368 +https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda#7c7927b404672409d9917d49bff5f2d6 +https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda#cae723309a49399d2949362f4ab5c9e4 +https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py310h25320af_0.conda#6ae8cc92dfb0b063519b9203445506af +https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda#9ce473d1d1be1cc3810856a48b3fab32 +https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2#961b3a227b437d82ad7054484cfa71b2 +https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda#86f7414544ae606282352fa1e116b41f +https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda#172bf1cd1ff8629f2b1179945ed45055 +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda#b499ce4b026493a13774bcf0f4c33849 +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda#eecce068c7e4eddeb169591baac20ac4 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.18.0-h4e3cde8_0.conda#0a5563efed19ca4461cf927419b6eb73 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.3-nompi_h2d575fe_109.conda#e7a7a6e6f70553a31e6e79c65768d089 +https://conda.anaconda.org/conda-forge/linux-64/h5py-3.13.0-nompi_py310h60e0fe6_100.conda#262cb7007454532e0cdf88c34c0c8f41 +https://conda.anaconda.org/conda-forge/linux-64/hdf5plugin-5.1.0-py310h03c0fba_0.conda#fe007df726508322378693e5d6612174 +https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda#e941e85e273121222580723010bd4fa2 +https://conda.anaconda.org/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda#f1e618f2f783427019071b14a111b30d +https://conda.anaconda.org/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda#6dc4e43174cd552452fdb8c423e90e69 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.2-pyhcf101f3_0.conda#4fefefb892ce9cc1539405bec2f1a6cd +https://conda.anaconda.org/conda-forge/noarch/pint-0.24.4-pyhe01879c_2.conda#7c7e5db36556343121c7baabcfdd85f6 +https://conda.anaconda.org/conda-forge/noarch/nxmx-0.0.4-pyhd8ed1ab_1.conda#764e361b981796d8f88ed8780bd68c32 +https://conda.anaconda.org/conda-forge/noarch/ordered-set-4.1.0-pyhd8ed1ab_1.conda#a130daf1699f927040956d3378baf0f2 +https://conda.anaconda.org/conda-forge/linux-64/pycbf-0.9.6.7-py310h7c4b9e2_2.conda#ee46eef6e45c8e42c2201b010b51f240 +https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda#e5ce43272193b38c2e9037446c1d9206 +https://conda.anaconda.org/conda-forge/linux-64/dxtbx-3.24.1-py310h848b074_0.conda#02a7f9506c944eb2551a057793afa605 +https://conda.anaconda.org/conda-forge/linux-64/ffbidx-1.1.3-py310h4f903cd_4.conda#a2a832eb8d57b066ca19f1b8db54591a +https://conda.anaconda.org/conda-forge/linux-64/gemmi-0.7.4-py310hba69e1a_2.conda#d278966af1cb1d4b9278a6fa102bd130 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py310h3406613_0.conda#8854df4fb4e37cc3ea0a024e48c9c180 +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda#04558c96691bed63104678757beb4f8d +https://conda.anaconda.org/conda-forge/linux-64/msgpack-cxx-7.0.0-h9fb26e6_0.conda#f54c289a1891eff554352e97306d8c25 +https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py310h0158d43_2.conda#0610ed073acc4737d036125a5a6dbae2 +https://conda.anaconda.org/conda-forge/noarch/procrunner-2.3.3-pyhd8ed1ab_1.conda#1c6c47faf82aea51d5beb1b4e38f3bd5 +https://conda.anaconda.org/conda-forge/linux-64/libopengl-devel-1.7.0-ha4b6fd6_2.conda#75b039b1e51525f4572f828be8441970 +https://conda.anaconda.org/conda-forge/noarch/pyopengl-3.1.10-pyha804496_2.conda#cce156023226a62044a8112bebf3d5e1 +https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda#615de2a4d97af50c350e5cf160149e77 +https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda#9d64911b31d57ca443e9f1e36b04385f +https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.7.2-py310h228f341_0.conda#0f3e3324506bd3e67934eda9895f37a7 +https://conda.anaconda.org/conda-forge/noarch/tabulate-0.9.0-pyhcf101f3_3.conda#de98449f11d48d4b52eefb354e2bfe35 +https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda#70e3400cbbfa03e96dcde7fc13e38c7b +https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda#9314bc5a1fe7d1044dc9dfd3ef400535 +https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_2.conda#c151d5eb730e9b7480e6d48c0fc44048 +https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_2.conda#c8013e438185f33b13814c5c488acd5c +https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda#928b8be80851f5d8ffb016f9c81dae7a +https://conda.anaconda.org/conda-forge/linux-64/xorg-xorgproto-2025.1-hb03c661_0.conda#aa8d21be4b461ce612d8f5fb791decae +https://conda.anaconda.org/conda-forge/linux-64/libglx-devel-1.7.0-ha4b6fd6_2.conda#27ac5ae872a21375d980bd4a6f99edf3 +https://conda.anaconda.org/conda-forge/linux-64/libgl-devel-1.7.0-ha4b6fd6_2.conda#53e7cbb2beb03d69a478631e23e340e9 +https://conda.anaconda.org/conda-forge/linux-64/libegl-devel-1.7.0-ha4b6fd6_2.conda#b513eb83b3137eca1192c34bf4f013a7 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda#b5fcc7172d22516e1f965490e65e33a4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxxf86vm-1.1.7-hb03c661_0.conda#665d152b9c6e78da404086088077c844 +https://conda.anaconda.org/conda-forge/linux-64/epoxy-1.5.10-hb03c661_2.conda#057083b06ccf1c2778344b6dabace38b +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.86.4-hf516916_1.conda#b52b769cd13f7adaa6ccdc68ef801709 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda#d4a250da4737ee127fb1fa6452a9002e +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-he237659_1.conda#417955234eccd8f252b86a265ccdab7f +https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.47-hb03c661_0.conda#b56e0c8432b56decafae7e78c5f29ba5 +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.1-hca5e8e5_0.conda#2bca1fbb221d9c3c8e3a155784bbc2e9 +https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-hd6090a7_1.conda#035da2e4f5770f036ff704fa17aace24 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.7-hb03c661_0.conda#f2ba4192d38b6cef2bb2c25029071d90 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda#2ccd714aa2242315acaf0a67faea780b +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxinerama-1.1.6-hecca717_0.conda#93f5d4b5c17c8540479ad65f206fea51 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-hb03c661_0.conda#e192019153591938acf7322b6459d36e +https://conda.anaconda.org/conda-forge/linux-64/gtk3-3.24.43-ha5ea40c_7.conda#f605332e1e4d9ff5c599933ae81db57d +https://conda.anaconda.org/conda-forge/linux-64/glib-2.86.4-h5192d8d_1.conda#61272bde04aeccf919351b92fbb96a53 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.26.10-h17cb667_0.conda#0c38cdf4414540aae129822f961b5636 +https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.5-hd0c01bc_1.conda#68e52064ed3897463c0e958ab5c8f91b +https://conda.anaconda.org/conda-forge/linux-64/libopus-1.6.1-h280c20c_0.conda#2446ac1fe030c2aa6141386c1f5a6aed +https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h54a6638_2.conda#b4ecbefe517ed0157c37f8182768271c +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxshmfence-1.3.3-hb9d3cd8_0.conda#9a809ce9f65460195777f2f2116bae02 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.26.10-h0363672_0.conda#fd9738c3189541787bd967e19587de26 +https://conda.anaconda.org/conda-forge/linux-64/wxwidgets-3.2.8.1-he9b06eb_2.conda#c8dd4c54e929b823ae7739dd556792e0 +https://conda.anaconda.org/conda-forge/linux-64/wxpython-4.2.4-py310hd8b2e20_0.conda#235f32b7a96e5e4f0c12d77b2e49142b +https://conda.anaconda.org/conda-forge/linux-64/dials-3.24.1-py310heeeba11_0.conda#097789d255c5799054523cdcd968376c +https://conda.anaconda.org/conda-forge/noarch/dill-0.4.1-pyhcf101f3_0.conda#080a808fce955026bf82107d955d32da +https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.4.0-hecca717_0.conda#dbe3ec0f120af456b3477743ffd99b74 +https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda#ff9efb7f7469aed3c4a8106ffa29593c +https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda#87e6096ec6d542d1c1f8b33245fe8300 +https://conda.anaconda.org/conda-forge/linux-64/lxml-6.0.2-py310he6d4be0_2.conda#4c8d8b7a0e2f506d4187d4a810595bd9 +https://conda.anaconda.org/conda-forge/noarch/glymur-0.13.8-pyhd8ed1ab_0.conda#63a147620b3562c1430014ca02d9f35a +https://conda.anaconda.org/conda-forge/linux-64/fabio-2025.10.0-py310hf779ad0_1.conda#126d978b841c9749bcd5433cd15b62ae +https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda#d3549fd50d450b6d9e7dddff25dd2110 +https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda#b8993c19b0c32a2f7b66cbb58ca27069 +https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.7-py310hfe99b16_0.conda#b73fbb2d98d4a44b1b63f382ec3e7e59 +https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-lite-2019.12.3-py310h261611a_8.conda#a774d86477e90322a714a21177ba6322 +https://conda.anaconda.org/conda-forge/noarch/tifffile-2020.6.3-py_0.tar.bz2#1fb771bb25b2eecbc73abf5143fa35bd +https://conda.anaconda.org/conda-forge/noarch/h5grove-2.3.0-pyhd8ed1ab_1.conda#1fea78799c996e31c03e360e7d670982 +https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda#03fe290994c5e4ec17293cfb6bdce520 +https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda#4f14640d58e2cc0aa0819d9d8ba125bb +https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda#d6989ead454181f4f9bc987d3dc4e285 +https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda#97c1ce2fffa1209e7afb432810ec6e12 +https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda#a4f4c5dc9b80bc50e0d3dc4e6e8f1bd9 +https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda#019a7385be9af33791c989871317e1ed +https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda#00e120ce3e40bad7bfc78861ce3c4a25 +https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda#7d9daffbb8d8e0af0f769dbbcd173a54 +https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda#d0d408b1f18883a944376da5cf8101ea +https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda#11a9d1d09a3615fc07c3faf79bc0b943 +https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda#c3197f8c0d5b955c904616b716aca093 +https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda#edb16f14d920fb3faf17f5ce582942d6 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda#6b6ece66ebcae2d5f326c77ef2c5a066 +https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda#3bfdfb8dbcdc4af1ae3f9a8eb3948f04 +https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda#b1b505328da7a6b246787df4b5a49fbc +https://conda.anaconda.org/conda-forge/noarch/ipython-8.37.0-pyh8f84b5b_0.conda#177cfa19fe3d74c87a8889286dc64090 +https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda#b38fe4e78ee75def7e599843ef4c1ab0 +https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda#a587892d3c13b6621a6091be690dbca2 +https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda#8035e5b54c08429354d5d64027041cad +https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py310hc4bea81_2.conda#0731121636c788d581db487531d53928 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.4-py310ha78b2d2_0.conda#4e11295b6114ce0d2b8984a3fa61f936 +https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda#8a3d6d0523f66cf004e563a50d9392b3 +https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda#598fd7d4d0de2455fb74f56063969a97 +https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda#8b267f517b81c13594ed68d646fd5dcb +https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda#bd80ba060603cc228d9d81c257093119 +https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda#0b0154421989637d424ccf0f104be51a +https://conda.anaconda.org/conda-forge/noarch/json5-0.13.0-pyhd8ed1ab_0.conda#8d5f66ebf832c4ce28d5c37a0e76605c +https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.0.0-pyhcf101f3_3.conda#cd2214824e36b0180141d422aba01938 +https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py310hd8f68c5_0.conda#61ff3f8e00c63bb66903636d0197e962 +https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda#870293df500ca7e18bedefa5838a22ab +https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda#439cd0f567d697b20a8f45cb70a1005a +https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda#ada41c863af263cc4c5fcbaff7c3e4dc +https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda#36de09a8d3e5d5e6f4ee63af49e59706 +https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2#912a71cc01012ee38e6b90ddd561e36f +https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda#9b965c999135d43a3d0f7bd7d024e26a +https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda#7234f99325263a5af6d4cd195035e8f2 +https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda#e7cb0f5745e4c5035a460248334af7eb +https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda#6639b6b0d8b5a284f027a2003669aa65 +https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda#8368d58342d0825f0843dc6acdd0c483 +https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda#a61bf9ec79426938ff785eb69dbb1960 +https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda#a77f85f77be52ff59391544bfe73390a +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py310h3406613_1.conda#2160894f57a40d2d629a34ee8497795f +https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda#31e11c30bbee1682a55627f953c6725a +https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda#17b43cee5cc84969529d5d0b0309b2cb +https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda#7b8bace4943e0dc345fc45938826f2b8 +https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda#fd312693df06da3578383232528c468d +https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda#b11e360fc4de2b0035fc8aaa74f17fd6 +https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda#23029aae904a2ba587daba708208012f +https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda#bbe1963f1e47f594070ffe87cdf612ea +https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda#00f5b8dafa842e0c27c1cd7296aa4875 +https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2#457c2c8c08e54905d6954e79cb5b5db9 +https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.0-pyhcf101f3_0.conda#b14079a39ae60ac7ad2ec3d9eab075ca +https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda#f6d7aa696c67756a650e91e15e88223c +https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda#e51f1e4089cad105b6cac64bd8166587 +https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.24.1-pyhd8ed1ab_0.conda#7526d20621b53440b0aae45d4797847e +https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda#28eb91468df04f655a57bcfbb35fc5c5 +https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda#2f1ed718fcd829c184a6d4f0f2e07409 +https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda#d79a87dcfa726bcea8e61275feed6f83 +https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda#62b7c96c6cd77f8173cc5cada6a9acaa +https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda#a63877cb23de826b1620d3adfccc4014 +https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda#e7f89ea5f7ea9401642758ff50a2d9c1 +https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.5-pyhd8ed1ab_0.conda#c4b96d937d1b03203c00fed92e713cd1 +https://conda.anaconda.org/conda-forge/noarch/jupyterlab-h5web-12.6.1-pyhd8ed1ab_0.conda#74e7c5058b1f7d830e0ad3b3418a3a1a +https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 +https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.8-hf7376ad_0.conda#1a2708a460884d6861425b7f9a7bef99 +https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.8-default_h99862b1_3.conda#24a2802074d26aecfdbc9b3f1d8168d1 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.8-default_h746c552_3.conda#b4277f5a09d458a0306db3147bd0171c +https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda#a1cfcc585f0c42bf8d5546bb1dfb668d +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.5.0-he200343_1.conda#47595b9d53054907a00d95e4d47af1d6 +https://conda.anaconda.org/conda-forge/linux-64/libunistring-0.9.10-h7f98852_0.tar.bz2#7245a044b4a1980ed83196176b78b73a +https://conda.anaconda.org/conda-forge/linux-64/libidn2-2.3.8-hfac485b_1.conda#842a81de672ddcf476337c8bde3cad33 +https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda#2e5bf4f1da39c0b32778561c3c4e5878 +https://conda.anaconda.org/conda-forge/linux-64/libpq-18.2-hb80d175_0.conda#fa63c385ddb50957d93bdb394e355be8 +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.32.9-hc50e24c_0.conda#c7f302fd11eeb0987a6a5e1f3aed6a21 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.2-hc7d488a_2.conda#067590f061c9f6ea7e61e3b2112ed6b3 +https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.341.0-h5279c79_0.conda#31ad065eda3c2d88f8215b1289df9c89 +https://conda.anaconda.org/conda-forge/noarch/uncertainties-3.2.4-pyhd8ed1ab_0.conda#6a0480bcfc93c9fbd0a19466fb9419d2 +https://conda.anaconda.org/conda-forge/noarch/lmfit-1.3.4-pyhd8ed1ab_0.conda#f8cdc37d08f88f8cd64f1252ecb6a7a9 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda#fdc27cb255a7a2cc73b7919a968b48f0 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda#a0901183f08b6c7107aab109733a3c91 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda#0e0cbe0564d03a99afd5fd7b362feecd +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda#4d1fc190b99912ed557a8236e958c559 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda#ad748ccca349aec3e91743e08b5e2b50 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.2-hb711507_0.conda#608e0ef8256b81d04456e8d211eee3e8 +https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.10.2-hb82b983_4.conda#9861c7820fdb45bc50a2ea60f4ff7952 +https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.10.2-py310h2007e60_0.conda#a68bc026bb8e8f1ec2a6ff6332e6db87 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.8-py310hff52083_0.conda#e78bcae4f58d0000f756c3b42da20f13 +https://conda.anaconda.org/conda-forge/noarch/mplcursors-0.7-pyhd8ed1ab_0.conda#bbabdd6f10fa142fcce310f39e050438 +https://conda.anaconda.org/conda-forge/noarch/nexusformat-2.0.2-pyhd8ed1ab_0.conda#5dc6b352ab02e6777abc24f14d5d31a7 +https://conda.anaconda.org/conda-forge/noarch/pylatexenc-2.10-pyhd8ed1ab_1.conda#93ca05ac99320ff2c95d73f5613859ff +https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda#fd5062942bfa1b0bd5e0d2a4397b099e +https://conda.anaconda.org/conda-forge/linux-64/sip-6.10.0-py310hea6c23e_1.conda#1a395a5ab0bf1d6f1e4757e1d9ec9168 +https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhcf101f3_3.conda#d0fc809fa4c4d85e959ce4ab6e1de800 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.17.0-py310hea6c23e_2.conda#f19f2739d411a1c19d231bfb7b83ec74 +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.38-h29cc59b_0.conda#e235d5566c9cc8970eb2798dd4ecf62f +https://conda.anaconda.org/conda-forge/linux-64/nss-3.118-h445c969_0.conda#567fbeed956c200c1db5782a424e58ee +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-client-17.0-h9a6aba3_3.conda#b8ea447fdf62e3597cb8d2fae4eb1a90 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.15-hc240232_7.conda#fa3bbe293d907990f3ca5b8b9d4b10f0 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.11-py310h046fae5_2.conda#21f8a5937ece568b9bdb611f01216cb9 +https://conda.anaconda.org/conda-forge/noarch/qtpy-2.4.3-pyhd8ed1ab_1.conda#b49c000df5aca26d36b3f078ba85e03a +https://conda.anaconda.org/conda-forge/noarch/qtconsole-base-5.7.1-pyha770c72_0.conda#07f747036a6dcd990cef27fe1a7dfa69 +https://conda.anaconda.org/conda-forge/noarch/qtconsole-5.7.1-pyhd8ed1ab_0.conda#aac9778cd8cce9811ca1961c8800c1a7 +https://conda.anaconda.org/conda-forge/noarch/nexpy-2.0.1-pyhd8ed1ab_0.conda#f2613a7e4f494ba9e8124958a7203b15 +https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2#9a66894dfd07c4510beb6b3f9672ccc0 +https://conda.anaconda.org/conda-forge/linux-64/numexpr-2.14.1-py310h34a7263_101.conda#cb02b04ff05ba415b55995bbcc82358f +https://conda.anaconda.org/conda-forge/linux-64/tar-1.35-h3b78370_0.conda#8e037804a92c84cb901bdd83a8e5ff46 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda#c9f075ab2f33b3bbee9e62d4ad0a6cd8 +https://conda.anaconda.org/conda-forge/linux-64/wget-1.21.4-hda4d442_0.conda#361e96b664eac64a33c20dfd11affbff +https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda#145c6f2ac90174d9ad1a2a51b9d7c1dd +https://conda.anaconda.org/conda-forge/noarch/xia2-3.24.1-pyhd8ed1ab_0.conda#81b98e7e348ee3c3d837f2d8c35f1c7e From cdf222130d188f4361c341485d128f0f79c76d2e Mon Sep 17 00:00:00 2001 From: Abdelsalam Date: Sat, 2 May 2026 16:37:31 +0300 Subject: [PATCH 2/8] Remove broken docker job from workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-existing docker: job was added 2026-03-25 (PR #57's revert era) and has never successfully pushed an image to Dockerhub. Two pre-existing bugs: file: dockerfile (lowercase, doesn't exist on Linux runners), and the login step ran unconditionally with creds that weren't set until 2026-05-02. The image it would build (.github/Dockerfile, the standalone Jupyter Lab launcher tagged :latest / :1.0.0) is not used anywhere in the Diffuse deployment chain — it's only referenced by mdx2-workflows/Dockerfile as a base for local Prefect dev, and that base image already exists on Dockerhub from a manual push in March 2026. Removing the dead job leaves the workflow with one focused, working job and clears the perpetual red X on every PR. The .github/Dockerfile is kept around as a reference if anyone wants to revive :1.0.0 builds later in a focused PR. --- .github/workflows/docker.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f6e730e..b79b470 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -7,40 +7,6 @@ on: pull_request: jobs: - docker: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Determine Docker tags - id: tags - run: | - if [ "${{ github.ref }}" = "refs/heads/main" ]; then - echo "tags=${{ vars.DOCKERHUB_USERNAME }}/mdx2:latest" >> $GITHUB_OUTPUT - else - echo "tags=${{ vars.DOCKERHUB_USERNAME }}/mdx2:test" >> $GITHUB_OUTPUT - fi - - - name: Build and push - uses: docker/build-push-action@v6 - with: - context: . - file: dockerfile - push: ${{ github.ref == 'refs/heads/main' }} - tags: ${{ steps.tags.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max - notebook: name: notebook image (singleuser) runs-on: ubuntu-latest From 14ec2e8d5b3b6f0c425d46760b210e5df68a3979 Mon Sep 17 00:00:00 2001 From: Abdelsalam Date: Sat, 2 May 2026 17:08:29 +0300 Subject: [PATCH 3/8] Strip dead weight from notebook image - Drop python_stage: nothing in the image invokes /usr/local/bin/python3.10; the conda env at /root/micromamba/envs/mdx2-dev/bin supplies the canonical Python (3.10.19, matching dxtbx 3.24.1) - Drop /opt/conda mkdir and PATH entry: the directory was never populated - Remove .git from cloned source so `git status` inside the running pod doesn't surface stale build-context state Image shrinks ~50 MB (4.66 GB to 4.61 GB uncompressed). Lockfile parity preserved: normalized md5 of conda list unchanged at d5efd15da8bcbddebfa20b20ef2f2ff6. --- Dockerfile.notebook | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Dockerfile.notebook b/Dockerfile.notebook index 9951cf4..d75393c 100644 --- a/Dockerfile.notebook +++ b/Dockerfile.notebook @@ -14,8 +14,6 @@ # built from. mdx2/VERSION=1.0.2. Always use the full 40-char SHA — Docker's # `git clone` defaults to a shallow clone that may not resolve short SHAs. -FROM python:3.10-slim AS python_stage - FROM mambaorg/micromamba:1.5.5 AS micromamba_stage # Isolated stage: clone mdx2 at the pinned commit. Keeps git out of the final image. @@ -34,7 +32,8 @@ RUN apt-get update \ RUN git clone https://github.com/diff-use/mdx2.git /tmp/mdx2 \ && cd /tmp/mdx2 \ && git fetch origin "${MDX2_PR_REF}:refs/heads/source-pin" \ - && git checkout "${MDX2_COMMIT}" + && git checkout "${MDX2_COMMIT}" \ + && rm -rf .git FROM debian:stable-slim AS final @@ -46,14 +45,11 @@ RUN apt-get update \ rsync \ && rm -rf /var/lib/apt/lists/* -COPY --from=python_stage /usr/local /usr/local COPY --from=micromamba_stage /bin/micromamba /usr/local/bin/micromamba ENV MAMBA_ROOT_PREFIX="/root/micromamba" \ CONDA_PREFIX="/root/micromamba/envs/mdx2-dev" \ - PATH="/root/micromamba/envs/mdx2-dev/bin:/usr/local/bin:/opt/conda/bin:$PATH" - -RUN mkdir -p /opt/conda + PATH="/root/micromamba/envs/mdx2-dev/bin:/usr/local/bin:$PATH" WORKDIR /home/dev From 3249501adfe0169a44a310b7e660d661c2ff1308 Mon Sep 17 00:00:00 2001 From: Abdelsalam Date: Sat, 2 May 2026 17:40:01 +0300 Subject: [PATCH 4/8] Add workflow guards: env vars, immutability check, paths, permissions Hoist IMAGE_NAME, IMAGE_TAG, and MDX2_COMMIT to workflow-level env so they're a single review surface for "what version are we publishing." Add a pre-push step that fails if the target tag already exists on Dockerhub, preventing silent overwrites of published immutable tags. Add a paths filter to the push trigger so only image-relevant changes trigger a build-and-push on main; pull_request stays unfiltered for predictable required-status-check behavior. Set permissions to least-privilege (contents:read for checkout, actions:write for type=gha cache). --- .github/workflows/docker.yml | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b79b470..3a3735d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,8 +4,21 @@ on: push: branches: - main + paths: + - 'Dockerfile.notebook' + - 'notebook-env.lock' + - '.github/workflows/docker.yml' pull_request: +permissions: + contents: read + actions: write # for type=gha cache writes + +env: + IMAGE_NAME: diffuseproject/mdx2 + IMAGE_TAG: 1.0.2-1 + MDX2_COMMIT: 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 + jobs: notebook: name: notebook image (singleuser) @@ -24,14 +37,25 @@ jobs: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Refuse to overwrite an already-published tag + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: | + if docker manifest inspect "${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}" >/dev/null 2>&1; then + echo "::error::Tag ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} already exists on Dockerhub." + echo "::error::Image tags are immutable. Bump IMAGE_TAG in .github/workflows/docker.yml" + echo "::error::(e.g. 1.0.2-1 -> 1.0.2-2 for stack/lockfile changes; 1.0.3-1 for mdx2 source bump) and merge again." + exit 1 + fi + echo "Tag ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} is free to publish." + - name: Build (and push on main) uses: docker/build-push-action@v6 with: context: . file: Dockerfile.notebook build-args: | - MDX2_COMMIT=327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 + MDX2_COMMIT=${{ env.MDX2_COMMIT }} push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - tags: diffuseproject/mdx2:1.0.2-1 + tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} cache-from: type=gha,scope=notebook cache-to: type=gha,mode=max,scope=notebook From fc5146724929eb1ffa0d3011a84a846748f9c4cc Mon Sep 17 00:00:00 2001 From: Abdelsalam Date: Sat, 2 May 2026 17:51:28 +0300 Subject: [PATCH 5/8] Document MDX2_COMMIT: what the SHA is and when to bump it --- .github/workflows/docker.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3a3735d..47f94fe 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,6 +17,12 @@ permissions: env: IMAGE_NAME: diffuseproject/mdx2 IMAGE_TAG: 1.0.2-1 + # HEAD of feat/jupyterhub-singleuser (PR #56 source ref) on 2026-02-25 — + # the commit diffuseproject/mdx2:test was originally built from + # (mdx2/VERSION=1.0.2). Reachable only via refs/pull/56/head since the + # branch was deleted post-merge; the Dockerfile fetches that ref + # explicitly. Bump together with IMAGE_TAG when shipping an mdx2 source + # upgrade (e.g. v1.0.4 -> 07f074a, IMAGE_TAG 1.0.2-1 -> 1.0.4-1). MDX2_COMMIT: 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 jobs: From daaa8c193317b2ee30fade2bb1d5af44b82833c6 Mon Sep 17 00:00:00 2001 From: Abdelsalam Date: Sun, 3 May 2026 09:15:48 +0300 Subject: [PATCH 6/8] Add upgrade guide for the notebook image Recipe-style operational doc under docs/ covering the three upgrade scenarios (mdx2 source bump, conda env refresh, OS apt addition) with concrete commands, rollback procedure, and common pitfalls. The 1.0.2 -> 1.0.4 upgrade walkthrough doubles as the cleanup procedure for the PR-ref scaffolding (MDX2_PR_REF + git fetch origin pull/56/head), which becomes deletable as soon as we move off the 327bf6e pin. Cluster SSH endpoint is referred to as ; actual value lives in the diff-use/infra Pulumi config (private repo). --- docs/upgrading-the-notebook-image.md | 304 +++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 docs/upgrading-the-notebook-image.md diff --git a/docs/upgrading-the-notebook-image.md b/docs/upgrading-the-notebook-image.md new file mode 100644 index 0000000..64a8eff --- /dev/null +++ b/docs/upgrading-the-notebook-image.md @@ -0,0 +1,304 @@ +# Upgrading the notebook image (`diffuseproject/mdx2:`) + +This guide covers how to publish a new version of the JupyterHub singleuser image — the image users get when they spawn a notebook in the Diffuse JupyterHub deployment. + +If you just want the runbook, jump to **[Upgrade scenarios](#upgrade-scenarios)** below. + +## When to use this guide + +Use this guide when you want to: + +- Ship a new version of mdx2 (e.g. v1.0.4) to JupyterHub users +- Refresh the conda environment (DIALS bump, security patches, quarterly refresh) +- Add an OS package to the image (apt deps for new tooling) +- Fix a bug in the image's build process + +**Do not use this guide** for: + +- Pure documentation changes (won't trigger the workflow on `main`; no image rebuild needed) +- Changes to other Dockerfiles in the repo (`Dockerfile`, `Dockerfile.source`, `.github/Dockerfile`) — those serve different purposes and have their own (or no) build pipelines + +## How the pipeline is wired + +``` +.github/workflows/docker.yml # CI/CD + env: + IMAGE_NAME = diffuseproject/mdx2 + IMAGE_TAG = - # e.g. 1.0.2-1 + MDX2_COMMIT = # mdx2 source commit baked in + +Dockerfile.notebook # multi-stage build + ↓ pulls notebook-env.lock # frozen conda env (385 packages) + ↓ git clone mdx2 @ MDX2_COMMIT # frozen mdx2 source + ↓ pip install -e . + jupyterhub # editable mdx2 + pinned pip deps + ↓ apt: openssh + sftp + rsync # SSH gateway tooling + +→ pushes to diffuseproject/mdx2:${IMAGE_TAG} on merge to main +``` + +Tags follow Debian-revision style: `-`. The trailing `-N` increments when our build changes but upstream mdx2 stays put. Tags are **immutable** — the workflow refuses to overwrite an existing tag. + +## Tag scheme decision tree + +``` +What's changing? +├── Just `notebook-env.lock` (e.g. DIALS bump) +│ → bump trailing -N: 1.0.2-1 → 1.0.2-2 +├── Just MDX2_COMMIT (mdx2 source bump, no new deps) +│ → bump upstream + reset rev: 1.0.2-1 → 1.0.3-1 +├── Both (mdx2 needs new conda packages) +│ → bump upstream + reset rev: 1.0.2-1 → 1.0.3-1 +└── Just OS apt packages (rare) + → bump trailing -N: 1.0.2-1 → 1.0.2-2 +``` + +## Upgrade scenarios + +Pick the one that matches your situation. + +### Scenario A: bump mdx2 source (e.g. v1.0.2 → v1.0.4) + +Use this when mdx2 ships a new version and you want JupyterHub users to get it. + +#### 1. Find the new MDX2_COMMIT + +mdx2 currently has no git tags (as of writing), so you pin to a commit SHA. Find the latest main HEAD: + +```bash +gh api /repos/diff-use/mdx2/commits/main --jq '.sha' +# → e.g. 07f074a3304baed3427a9cbf18b311f82af6af37 +``` + +Verify the commit's `mdx2/VERSION`: + +```bash +gh api '/repos/diff-use/mdx2/contents/mdx2/VERSION?ref=07f074a3304baed3427a9cbf18b311f82af6af37' --jq '.content' | base64 -d +# → 1.0.4 +``` + +#### 2. Clean up the PR-ref scaffolding (one-time, when moving off the 1.0.2 pin) + +The current Dockerfile.notebook has special-case logic to fetch `refs/pull/56/head` because the original 1.0.2 commit lives only on a deleted branch. Once you upgrade off that commit, the PR-ref fetch is unnecessary scaffolding. Remove these lines from `Dockerfile.notebook`: + +```diff + FROM debian:stable-slim AS source_stage + ARG MDX2_COMMIT +-ARG MDX2_PR_REF=pull/56/head + RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates git \ + && rm -rf /var/lib/apt/lists/* + RUN git clone https://github.com/diff-use/mdx2.git /tmp/mdx2 \ + && cd /tmp/mdx2 \ +- && git fetch origin "${MDX2_PR_REF}:refs/heads/source-pin" \ + && git checkout "${MDX2_COMMIT}" \ + && rm -rf .git +``` + +Also drop the comment block above `MDX2_PR_REF` and the build-arg in the workflow if present. The `git clone` is now sufficient because future commits live on `main` (durable). + +#### 3. Update the workflow env + +Edit `.github/workflows/docker.yml`: + +```diff + env: + IMAGE_NAME: diffuseproject/mdx2 +- IMAGE_TAG: 1.0.2-1 ++ IMAGE_TAG: 1.0.4-1 +- # HEAD of feat/jupyterhub-singleuser (PR #56 source ref) on 2026-02-25 — ... +- MDX2_COMMIT: 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 ++ # mdx2 main HEAD as of , mdx2/VERSION=1.0.4 (PR #19 release merge). ++ MDX2_COMMIT: 07f074a3304baed3427a9cbf18b311f82af6af37 +``` + +#### 4. Local build + smoke test + +```bash +cd ~/ptown/mdx2-workflows/.claude/worktrees/ + +docker buildx build \ + --load \ + -f Dockerfile.notebook \ + -t mdx2-test-local:1.0.4-1 \ + --build-arg MDX2_COMMIT=07f074a3304baed3427a9cbf18b311f82af6af37 \ + . + +# Smoke tests +docker run --rm mdx2-test-local:1.0.4-1 sh -c \ + '/usr/local/bin/micromamba run -n mdx2-dev python -c "import mdx2; print(mdx2.__version__)"' +# → 1.0.4 + +docker run --rm mdx2-test-local:1.0.4-1 sh -c 'which scp; which rsync; ls /usr/lib/openssh/sftp-server' +# → all three present + +docker run --rm mdx2-test-local:1.0.4-1 sh -c \ + '/usr/local/bin/micromamba run -n mdx2-dev jupyterhub-singleuser --version' +# → 5.4.3 (or whatever pip version is pinned) +``` + +#### 5. Open PR, watch CI, merge + +Same as the original PR #1 flow. CI on the PR will build (no push). On merge to main, the immutability check passes (1.0.4-1 doesn't exist yet), the build runs, and the image pushes to Dockerhub. + +#### 6. Webapp cutover + +Single-line PR against `diff-use/webapp`: + +```diff +-jupyterhub_notebook_image_tag: str = "1.0.2-1" ++jupyterhub_notebook_image_tag: str = "1.0.4-1" +``` + +at `app/config.py:217`. Merge through normal review process. + +#### 7. Verify in prod + +After webapp deploys, restart the JupyterHub user notebook from the Hub Control Panel → Stop My Server → Start My Server. KubeSpawner pulls fresh because `imagePullPolicy: Always`. + +> **Note on ``**: this is the SSH endpoint of the Voltage Park machine that hosts the sampleworks k3s cluster. The actual hostname/IP is in the `diff-use/infra` Pulumi config (private repo); ask a maintainer or grep the infra repo for `sampleworks` to find it. + +```bash +ssh diffuse@ \ + 'sudo k3s kubectl -n jupyterhub get pod jupyter- \ + -o jsonpath="{.spec.containers[0].image}"; echo' +# → diffuseproject/mdx2:1.0.4-1 +``` + +### Scenario B: refresh the conda environment (DIALS bump, security patches) + +Use this when mdx2 source stays the same but you want newer scientific packages — typically a quarterly cadence to avoid bit-rot in conda-forge URLs. + +#### 1. Spin up a temp container with the current `env.yaml` + +The cleanest way to get a fresh solve is to let micromamba resolve `env.yaml` from scratch in an isolated container: + +```bash +docker run --rm -v "$PWD:/work" -w /work mambaorg/micromamba:1.5.5 bash -c ' + micromamba create -n tmp -f env.yaml --yes && \ + micromamba install -y -n tmp nexpy jupyterlab jupyterlab-h5web dials xia2 wget tar -c conda-forge && \ + micromamba env export --explicit -n tmp +' > notebook-env.lock.new +``` + +Then replace the old lockfile: + +```bash +mv notebook-env.lock.new notebook-env.lock +``` + +Inspect the diff: + +```bash +git diff notebook-env.lock | head -50 +# Look for DIALS, dxtbx, scipy, numpy version bumps +``` + +Document any meaningful version moves in the PR description (especially DIALS — file format defaults can change). + +#### 2. Bump the trailing revision in `IMAGE_TAG` + +```diff + env: +- IMAGE_TAG: 1.0.2-1 ++ IMAGE_TAG: 1.0.2-2 +``` + +Don't touch `MDX2_COMMIT` for a stack-only refresh. + +#### 3. Local build + smoke test + PR + cutover + +Same procedure as Scenario A from step 4 onward. + +#### Optional: regenerate from inside a running pod + +If you'd rather pin to whatever a currently-deployed pod has installed (e.g. capturing a hand-tweaked dev pod's state): + +```bash +ssh diffuse@ \ + 'sudo k3s kubectl -n jupyterhub exec -c notebook \ + -- /usr/local/bin/micromamba env export --explicit -n mdx2-dev' \ + > notebook-env.lock +``` + +Then bump `IMAGE_TAG` and proceed as above. + +### Scenario C: add an OS apt package + +Use this when scp/sftp-server-style additions are needed but mdx2 and the conda env stay put. + +#### 1. Edit `Dockerfile.notebook` + +```diff + RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + openssh-client \ + openssh-sftp-server \ + rsync \ ++ \ + && rm -rf /var/lib/apt/lists/* +``` + +#### 2. Bump the trailing revision + +```diff +- IMAGE_TAG: 1.0.2-1 ++ IMAGE_TAG: 1.0.2-2 +``` + +#### 3. Local build + smoke test + PR + cutover + +Same as Scenarios A and B. + +## Rollback + +If a `1.0.X-Y` image causes regressions in prod: + +1. Revert the webapp config PR (`app/config.py:217` back to the previous tag). +2. Deploy the revert through the webapp's normal pipeline. +3. Restart any JupyterHub user pod; it pulls the previous tag. +4. The bad image stays parked on Dockerhub (immutable, harmless), but no pod uses it. + +You don't need to delete or "fix" the broken image on Dockerhub. The webapp config is the source of truth for which tag prod uses; pointing it elsewhere effectively rolls back. + +## Common pitfalls + +### "Tag already exists on Dockerhub" error in CI + +You forgot to bump `IMAGE_TAG` for a change that touched `Dockerfile.notebook` or `notebook-env.lock`. Fix: edit the workflow, bump `IMAGE_TAG`, push another commit. The check is loud-by-design — the only failure mode it allows is "you publish what you intended to publish." + +### CI didn't run on a PR + +The workflow has a `paths:` filter on the `push:` trigger (only `Dockerfile.notebook`, `notebook-env.lock`, and the workflow itself trigger main builds). PR builds are unfiltered. If CI didn't run on your PR but you only changed those files, check that you're working in the right repo and branch. + +### Conda solve fails when regenerating the lockfile + +conda-forge occasionally retires very old build artifacts. If the temp-container regeneration step fails on a missing package, the upstream `env.yaml` may need a relaxation (e.g. `python >=3.10, <3.12` instead of `<3.11`). That's an upstream mdx2 change — coordinate with mdx2 maintainers before forcing it from this repo. + +### "I want the image to behave exactly like a previous version" + +You can't. Conda-forge resolves freshly each time, and `git clone` of mdx2 picks up the current commit at clone time. The lockfile is the only way to get byte-equivalence with a *previously captured* environment. If you need to rebuild the *same* image bytes, pull the existing tag from Dockerhub instead of rebuilding from source. + +### Webapp pod still using the old image after merge + +Two checks: + +1. Did the new image actually push? `curl -sS "https://hub.docker.com/v2/repositories/diffuseproject/mdx2/tags/" | jq '.results[] | {name, last_updated}'`. +2. Did KubeSpawner pull the new image? Stop the user's server from the Hub Control Panel and start it again. Default `imagePullPolicy: Always` means a fresh pull on each start. + +## Reference: current pipeline state + +| Item | Value | Source | +|---|---|---| +| Image registry | `diffuseproject/mdx2` | `IMAGE_NAME` in `.github/workflows/docker.yml` | +| Current tag | `1.0.2-1` | `IMAGE_TAG` in `.github/workflows/docker.yml` | +| mdx2 source pin | `327bf6e1541e3e0b63a22c8aff100b92c4aa6e39` (PR #56 head, deleted branch) | `MDX2_COMMIT` in `.github/workflows/docker.yml` | +| Conda env source | `notebook-env.lock` (385 packages, conda-forge linux-64) | repo root | +| pip pins | `jupyterhub==5.4.3`, `jupyter-vscode-proxy==0.7` | `Dockerfile.notebook` | +| Webapp consumer | `jupyterhub_notebook_image_tag` setting | `diff-use/webapp:app/config.py:217` | + +## Suggested follow-ups (not required for any specific upgrade) + +- **Ask mdx2 maintainers to start tagging releases.** The repo currently has zero git tags despite "v1.0.4" being a real release. Tagging would let us pin `MDX2_COMMIT: v1.0.4` instead of opaque SHAs. +- **Ask mdx2 maintainers to push the 1.0.2 source pin (`327bf6e1541e3e0b63a22c8aff100b92c4aa6e39`) as a durable git tag** (e.g. `singleuser-1.0.2-pin`). Currently we depend on `refs/pull/56/head` being kept by GitHub. The dependency disappears as soon as we upgrade off that commit, but until then a durable tag is insurance. +- **Quarterly lockfile refresh cadence.** Old conda-forge artifacts can disappear (rare, but happens). Regenerating `notebook-env.lock` every ~3 months keeps the URLs alive. From 7443d20304baa9a66d553a8f069757d81bafb292 Mon Sep 17 00:00:00 2001 From: Abdelsalam Date: Mon, 4 May 2026 22:48:35 +0300 Subject: [PATCH 7/8] Rename image to diffuseproject/mdx2-notebook The shared diffuseproject/mdx2 Dockerhub repo currently holds two unrelated images: :1.0.0 and :latest are the standalone Jupyter Lab launcher (saada's image, base for mdx2-workflows/Dockerfile), while :test (and now :1.0.2-1) are the JupyterHub singleuser image. Sharing a repo across two semantically different images, distinguished only by tag, is the kind of latent collision that bites during incidents. Move the new image to its own repo (diffuseproject/mdx2-notebook). The legacy :1.0.0/:latest tags stay where they are; this repo's Dockerfile and docker-compose.yml continue to reference them unchanged. Provenance comments referring to the legacy diffuseproject/mdx2:test tag are kept verbatim, since they document historical fact about a real Dockerhub image that still exists at the old name. --- .github/workflows/docker.yml | 2 +- Dockerfile.notebook | 2 +- docs/upgrading-the-notebook-image.md | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 47f94fe..5a7fa74 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -15,7 +15,7 @@ permissions: actions: write # for type=gha cache writes env: - IMAGE_NAME: diffuseproject/mdx2 + IMAGE_NAME: diffuseproject/mdx2-notebook IMAGE_TAG: 1.0.2-1 # HEAD of feat/jupyterhub-singleuser (PR #56 source ref) on 2026-02-25 — # the commit diffuseproject/mdx2:test was originally built from diff --git a/Dockerfile.notebook b/Dockerfile.notebook index d75393c..a9c445b 100644 --- a/Dockerfile.notebook +++ b/Dockerfile.notebook @@ -7,7 +7,7 @@ # Build: # docker buildx build \ # --build-arg MDX2_COMMIT=327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 \ -# -f Dockerfile.notebook -t diffuseproject/mdx2:1.0.2-1 . +# -f Dockerfile.notebook -t diffuseproject/mdx2-notebook:1.0.2-1 . # # 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 is the HEAD of feat/jupyterhub-singleuser # (PR #56 source ref) on 2026-02-25, which is what diffuseproject/mdx2:test was diff --git a/docs/upgrading-the-notebook-image.md b/docs/upgrading-the-notebook-image.md index 64a8eff..3457360 100644 --- a/docs/upgrading-the-notebook-image.md +++ b/docs/upgrading-the-notebook-image.md @@ -1,4 +1,4 @@ -# Upgrading the notebook image (`diffuseproject/mdx2:`) +# Upgrading the notebook image (`diffuseproject/mdx2-notebook:`) This guide covers how to publish a new version of the JupyterHub singleuser image — the image users get when they spawn a notebook in the Diffuse JupyterHub deployment. @@ -23,7 +23,7 @@ Use this guide when you want to: ``` .github/workflows/docker.yml # CI/CD env: - IMAGE_NAME = diffuseproject/mdx2 + IMAGE_NAME = diffuseproject/mdx2-notebook IMAGE_TAG = - # e.g. 1.0.2-1 MDX2_COMMIT = # mdx2 source commit baked in @@ -33,7 +33,7 @@ Dockerfile.notebook # multi-stage build ↓ pip install -e . + jupyterhub # editable mdx2 + pinned pip deps ↓ apt: openssh + sftp + rsync # SSH gateway tooling -→ pushes to diffuseproject/mdx2:${IMAGE_TAG} on merge to main +→ pushes to diffuseproject/mdx2-notebook:${IMAGE_TAG} on merge to main ``` Tags follow Debian-revision style: `-`. The trailing `-N` increments when our build changes but upstream mdx2 stays put. Tags are **immutable** — the workflow refuses to overwrite an existing tag. @@ -102,7 +102,7 @@ Edit `.github/workflows/docker.yml`: ```diff env: - IMAGE_NAME: diffuseproject/mdx2 + IMAGE_NAME: diffuseproject/mdx2-notebook - IMAGE_TAG: 1.0.2-1 + IMAGE_TAG: 1.0.4-1 - # HEAD of feat/jupyterhub-singleuser (PR #56 source ref) on 2026-02-25 — ... @@ -161,7 +161,7 @@ After webapp deploys, restart the JupyterHub user notebook from the Hub Control ssh diffuse@ \ 'sudo k3s kubectl -n jupyterhub get pod jupyter- \ -o jsonpath="{.spec.containers[0].image}"; echo' -# → diffuseproject/mdx2:1.0.4-1 +# → diffuseproject/mdx2-notebook:1.0.4-1 ``` ### Scenario B: refresh the conda environment (DIALS bump, security patches) @@ -283,14 +283,14 @@ You can't. Conda-forge resolves freshly each time, and `git clone` of mdx2 picks Two checks: -1. Did the new image actually push? `curl -sS "https://hub.docker.com/v2/repositories/diffuseproject/mdx2/tags/" | jq '.results[] | {name, last_updated}'`. +1. Did the new image actually push? `curl -sS "https://hub.docker.com/v2/repositories/diffuseproject/mdx2-notebook/tags/" | jq '.results[] | {name, last_updated}'`. 2. Did KubeSpawner pull the new image? Stop the user's server from the Hub Control Panel and start it again. Default `imagePullPolicy: Always` means a fresh pull on each start. ## Reference: current pipeline state | Item | Value | Source | |---|---|---| -| Image registry | `diffuseproject/mdx2` | `IMAGE_NAME` in `.github/workflows/docker.yml` | +| Image registry | `diffuseproject/mdx2-notebook` | `IMAGE_NAME` in `.github/workflows/docker.yml` | | Current tag | `1.0.2-1` | `IMAGE_TAG` in `.github/workflows/docker.yml` | | mdx2 source pin | `327bf6e1541e3e0b63a22c8aff100b92c4aa6e39` (PR #56 head, deleted branch) | `MDX2_COMMIT` in `.github/workflows/docker.yml` | | Conda env source | `notebook-env.lock` (385 packages, conda-forge linux-64) | repo root | From d002c88139e33788ebb818d3af0b2d8495f35901 Mon Sep 17 00:00:00 2001 From: Abdelsalam Date: Mon, 4 May 2026 23:38:30 +0300 Subject: [PATCH 8/8] Rename image and supporting files from notebook to jhub Roll the previous notebook->jhub naming through every dependent surface in one atomic commit: - diffuseproject/mdx2-notebook -> diffuseproject/mdx2-jhub (workflow IMAGE_NAME) - Dockerfile.notebook -> Dockerfile.jhub (git mv) - notebook-env.lock -> jhub-env.lock (git mv) - docs/upgrading-the-notebook-image.md -> docs/upgrading-the-jhub-image.md (git mv) - workflow paths filter, file: arg, job key, display name, cache scope all updated to match. Reasoning: image names that describe the operational role (jhub) are more specific than artifact-form names (notebook), and the team's verbal shorthand for this image is 'the jhub image'. Aligning the artifact name with how it is referred to in conversation removes one translation step between Slack/standup and the codebase. Preserved verbatim: prose references to 'a notebook' (user's Jupyter session, not the image), the kubernetes container name '-c notebook' (set in the JupyterHub deployment config and not under this repo's control), and 'jupyterhub_notebook_image_tag' (a field name in the diff-use/webapp config; renaming that is a separate webapp-side decision). --- .github/workflows/docker.yml | 16 +++---- Dockerfile.notebook => Dockerfile.jhub | 10 ++--- ...k-image.md => upgrading-the-jhub-image.md} | 44 +++++++++---------- notebook-env.lock => jhub-env.lock | 0 4 files changed, 35 insertions(+), 35 deletions(-) rename Dockerfile.notebook => Dockerfile.jhub (89%) rename docs/{upgrading-the-notebook-image.md => upgrading-the-jhub-image.md} (85%) rename notebook-env.lock => jhub-env.lock (100%) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5a7fa74..cff0de7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -5,8 +5,8 @@ on: branches: - main paths: - - 'Dockerfile.notebook' - - 'notebook-env.lock' + - 'Dockerfile.jhub' + - 'jhub-env.lock' - '.github/workflows/docker.yml' pull_request: @@ -15,7 +15,7 @@ permissions: actions: write # for type=gha cache writes env: - IMAGE_NAME: diffuseproject/mdx2-notebook + IMAGE_NAME: diffuseproject/mdx2-jhub IMAGE_TAG: 1.0.2-1 # HEAD of feat/jupyterhub-singleuser (PR #56 source ref) on 2026-02-25 — # the commit diffuseproject/mdx2:test was originally built from @@ -26,8 +26,8 @@ env: MDX2_COMMIT: 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 jobs: - notebook: - name: notebook image (singleuser) + jhub: + name: jhub image (singleuser) runs-on: ubuntu-latest steps: - name: Checkout repository @@ -58,10 +58,10 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: Dockerfile.notebook + file: Dockerfile.jhub build-args: | MDX2_COMMIT=${{ env.MDX2_COMMIT }} push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} - cache-from: type=gha,scope=notebook - cache-to: type=gha,mode=max,scope=notebook + cache-from: type=gha,scope=jhub + cache-to: type=gha,mode=max,scope=jhub diff --git a/Dockerfile.notebook b/Dockerfile.jhub similarity index 89% rename from Dockerfile.notebook rename to Dockerfile.jhub index a9c445b..d30836e 100644 --- a/Dockerfile.notebook +++ b/Dockerfile.jhub @@ -1,13 +1,13 @@ -# Notebook image for the Diffuse JupyterHub deployment (singleuser pods). +# JupyterHub singleuser image for the Diffuse deployment (notebook pods). # Reproduces diffuseproject/mdx2:test using a captured conda lockfile -# (notebook-env.lock) for byte-equivalent scientific stack parity with prod, +# (jhub-env.lock) for byte-equivalent scientific stack parity with prod, # plus openssh-client / openssh-sftp-server / rsync so scp/sftp/rsync work # through the SSH gateway. # # Build: # docker buildx build \ # --build-arg MDX2_COMMIT=327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 \ -# -f Dockerfile.notebook -t diffuseproject/mdx2-notebook:1.0.2-1 . +# -f Dockerfile.jhub -t diffuseproject/mdx2-jhub:1.0.2-1 . # # 327bf6e1541e3e0b63a22c8aff100b92c4aa6e39 is the HEAD of feat/jupyterhub-singleuser # (PR #56 source ref) on 2026-02-25, which is what diffuseproject/mdx2:test was @@ -54,9 +54,9 @@ ENV MAMBA_ROOT_PREFIX="/root/micromamba" \ WORKDIR /home/dev # Conda env from explicit lockfile — no solve, deterministic, byte-equivalent to live :test pod. -COPY notebook-env.lock /home/dev/notebook-env.lock +COPY jhub-env.lock /home/dev/jhub-env.lock RUN --mount=type=cache,target=/root/micromamba/pkgs \ - /usr/local/bin/micromamba create -n mdx2-dev --yes --file /home/dev/notebook-env.lock + /usr/local/bin/micromamba create -n mdx2-dev --yes --file /home/dev/jhub-env.lock COPY --from=source_stage /tmp/mdx2/ /home/dev/ diff --git a/docs/upgrading-the-notebook-image.md b/docs/upgrading-the-jhub-image.md similarity index 85% rename from docs/upgrading-the-notebook-image.md rename to docs/upgrading-the-jhub-image.md index 3457360..f16de39 100644 --- a/docs/upgrading-the-notebook-image.md +++ b/docs/upgrading-the-jhub-image.md @@ -1,4 +1,4 @@ -# Upgrading the notebook image (`diffuseproject/mdx2-notebook:`) +# Upgrading the jhub image (`diffuseproject/mdx2-jhub:`) This guide covers how to publish a new version of the JupyterHub singleuser image — the image users get when they spawn a notebook in the Diffuse JupyterHub deployment. @@ -23,17 +23,17 @@ Use this guide when you want to: ``` .github/workflows/docker.yml # CI/CD env: - IMAGE_NAME = diffuseproject/mdx2-notebook + IMAGE_NAME = diffuseproject/mdx2-jhub IMAGE_TAG = - # e.g. 1.0.2-1 MDX2_COMMIT = # mdx2 source commit baked in -Dockerfile.notebook # multi-stage build - ↓ pulls notebook-env.lock # frozen conda env (385 packages) +Dockerfile.jhub # multi-stage build + ↓ pulls jhub-env.lock # frozen conda env (385 packages) ↓ git clone mdx2 @ MDX2_COMMIT # frozen mdx2 source ↓ pip install -e . + jupyterhub # editable mdx2 + pinned pip deps ↓ apt: openssh + sftp + rsync # SSH gateway tooling -→ pushes to diffuseproject/mdx2-notebook:${IMAGE_TAG} on merge to main +→ pushes to diffuseproject/mdx2-jhub:${IMAGE_TAG} on merge to main ``` Tags follow Debian-revision style: `-`. The trailing `-N` increments when our build changes but upstream mdx2 stays put. Tags are **immutable** — the workflow refuses to overwrite an existing tag. @@ -42,7 +42,7 @@ Tags follow Debian-revision style: `-`. The trailing ``` What's changing? -├── Just `notebook-env.lock` (e.g. DIALS bump) +├── Just `jhub-env.lock` (e.g. DIALS bump) │ → bump trailing -N: 1.0.2-1 → 1.0.2-2 ├── Just MDX2_COMMIT (mdx2 source bump, no new deps) │ → bump upstream + reset rev: 1.0.2-1 → 1.0.3-1 @@ -78,7 +78,7 @@ gh api '/repos/diff-use/mdx2/contents/mdx2/VERSION?ref=07f074a3304baed3427a9cbf1 #### 2. Clean up the PR-ref scaffolding (one-time, when moving off the 1.0.2 pin) -The current Dockerfile.notebook has special-case logic to fetch `refs/pull/56/head` because the original 1.0.2 commit lives only on a deleted branch. Once you upgrade off that commit, the PR-ref fetch is unnecessary scaffolding. Remove these lines from `Dockerfile.notebook`: +The current Dockerfile.jhub has special-case logic to fetch `refs/pull/56/head` because the original 1.0.2 commit lives only on a deleted branch. Once you upgrade off that commit, the PR-ref fetch is unnecessary scaffolding. Remove these lines from `Dockerfile.jhub`: ```diff FROM debian:stable-slim AS source_stage @@ -102,7 +102,7 @@ Edit `.github/workflows/docker.yml`: ```diff env: - IMAGE_NAME: diffuseproject/mdx2-notebook + IMAGE_NAME: diffuseproject/mdx2-jhub - IMAGE_TAG: 1.0.2-1 + IMAGE_TAG: 1.0.4-1 - # HEAD of feat/jupyterhub-singleuser (PR #56 source ref) on 2026-02-25 — ... @@ -118,7 +118,7 @@ cd ~/ptown/mdx2-workflows/.claude/worktrees/ docker buildx build \ --load \ - -f Dockerfile.notebook \ + -f Dockerfile.jhub \ -t mdx2-test-local:1.0.4-1 \ --build-arg MDX2_COMMIT=07f074a3304baed3427a9cbf18b311f82af6af37 \ . @@ -161,7 +161,7 @@ After webapp deploys, restart the JupyterHub user notebook from the Hub Control ssh diffuse@ \ 'sudo k3s kubectl -n jupyterhub get pod jupyter- \ -o jsonpath="{.spec.containers[0].image}"; echo' -# → diffuseproject/mdx2-notebook:1.0.4-1 +# → diffuseproject/mdx2-jhub:1.0.4-1 ``` ### Scenario B: refresh the conda environment (DIALS bump, security patches) @@ -177,19 +177,19 @@ docker run --rm -v "$PWD:/work" -w /work mambaorg/micromamba:1.5.5 bash -c ' micromamba create -n tmp -f env.yaml --yes && \ micromamba install -y -n tmp nexpy jupyterlab jupyterlab-h5web dials xia2 wget tar -c conda-forge && \ micromamba env export --explicit -n tmp -' > notebook-env.lock.new +' > jhub-env.lock.new ``` Then replace the old lockfile: ```bash -mv notebook-env.lock.new notebook-env.lock +mv jhub-env.lock.new jhub-env.lock ``` Inspect the diff: ```bash -git diff notebook-env.lock | head -50 +git diff jhub-env.lock | head -50 # Look for DIALS, dxtbx, scipy, numpy version bumps ``` @@ -217,7 +217,7 @@ If you'd rather pin to whatever a currently-deployed pod has installed (e.g. cap ssh diffuse@ \ 'sudo k3s kubectl -n jupyterhub exec -c notebook \ -- /usr/local/bin/micromamba env export --explicit -n mdx2-dev' \ - > notebook-env.lock + > jhub-env.lock ``` Then bump `IMAGE_TAG` and proceed as above. @@ -226,7 +226,7 @@ Then bump `IMAGE_TAG` and proceed as above. Use this when scp/sftp-server-style additions are needed but mdx2 and the conda env stay put. -#### 1. Edit `Dockerfile.notebook` +#### 1. Edit `Dockerfile.jhub` ```diff RUN apt-get update \ @@ -265,11 +265,11 @@ You don't need to delete or "fix" the broken image on Dockerhub. The webapp conf ### "Tag already exists on Dockerhub" error in CI -You forgot to bump `IMAGE_TAG` for a change that touched `Dockerfile.notebook` or `notebook-env.lock`. Fix: edit the workflow, bump `IMAGE_TAG`, push another commit. The check is loud-by-design — the only failure mode it allows is "you publish what you intended to publish." +You forgot to bump `IMAGE_TAG` for a change that touched `Dockerfile.jhub` or `jhub-env.lock`. Fix: edit the workflow, bump `IMAGE_TAG`, push another commit. The check is loud-by-design — the only failure mode it allows is "you publish what you intended to publish." ### CI didn't run on a PR -The workflow has a `paths:` filter on the `push:` trigger (only `Dockerfile.notebook`, `notebook-env.lock`, and the workflow itself trigger main builds). PR builds are unfiltered. If CI didn't run on your PR but you only changed those files, check that you're working in the right repo and branch. +The workflow has a `paths:` filter on the `push:` trigger (only `Dockerfile.jhub`, `jhub-env.lock`, and the workflow itself trigger main builds). PR builds are unfiltered. If CI didn't run on your PR but you only changed those files, check that you're working in the right repo and branch. ### Conda solve fails when regenerating the lockfile @@ -283,22 +283,22 @@ You can't. Conda-forge resolves freshly each time, and `git clone` of mdx2 picks Two checks: -1. Did the new image actually push? `curl -sS "https://hub.docker.com/v2/repositories/diffuseproject/mdx2-notebook/tags/" | jq '.results[] | {name, last_updated}'`. +1. Did the new image actually push? `curl -sS "https://hub.docker.com/v2/repositories/diffuseproject/mdx2-jhub/tags/" | jq '.results[] | {name, last_updated}'`. 2. Did KubeSpawner pull the new image? Stop the user's server from the Hub Control Panel and start it again. Default `imagePullPolicy: Always` means a fresh pull on each start. ## Reference: current pipeline state | Item | Value | Source | |---|---|---| -| Image registry | `diffuseproject/mdx2-notebook` | `IMAGE_NAME` in `.github/workflows/docker.yml` | +| Image registry | `diffuseproject/mdx2-jhub` | `IMAGE_NAME` in `.github/workflows/docker.yml` | | Current tag | `1.0.2-1` | `IMAGE_TAG` in `.github/workflows/docker.yml` | | mdx2 source pin | `327bf6e1541e3e0b63a22c8aff100b92c4aa6e39` (PR #56 head, deleted branch) | `MDX2_COMMIT` in `.github/workflows/docker.yml` | -| Conda env source | `notebook-env.lock` (385 packages, conda-forge linux-64) | repo root | -| pip pins | `jupyterhub==5.4.3`, `jupyter-vscode-proxy==0.7` | `Dockerfile.notebook` | +| Conda env source | `jhub-env.lock` (385 packages, conda-forge linux-64) | repo root | +| pip pins | `jupyterhub==5.4.3`, `jupyter-vscode-proxy==0.7` | `Dockerfile.jhub` | | Webapp consumer | `jupyterhub_notebook_image_tag` setting | `diff-use/webapp:app/config.py:217` | ## Suggested follow-ups (not required for any specific upgrade) - **Ask mdx2 maintainers to start tagging releases.** The repo currently has zero git tags despite "v1.0.4" being a real release. Tagging would let us pin `MDX2_COMMIT: v1.0.4` instead of opaque SHAs. - **Ask mdx2 maintainers to push the 1.0.2 source pin (`327bf6e1541e3e0b63a22c8aff100b92c4aa6e39`) as a durable git tag** (e.g. `singleuser-1.0.2-pin`). Currently we depend on `refs/pull/56/head` being kept by GitHub. The dependency disappears as soon as we upgrade off that commit, but until then a durable tag is insurance. -- **Quarterly lockfile refresh cadence.** Old conda-forge artifacts can disappear (rare, but happens). Regenerating `notebook-env.lock` every ~3 months keeps the URLs alive. +- **Quarterly lockfile refresh cadence.** Old conda-forge artifacts can disappear (rare, but happens). Regenerating `jhub-env.lock` every ~3 months keeps the URLs alive. diff --git a/notebook-env.lock b/jhub-env.lock similarity index 100% rename from notebook-env.lock rename to jhub-env.lock