diff --git a/.github/workflows/build.ps1 b/.github/workflows/build.ps1
index ba77c57..7f375da 100644
--- a/.github/workflows/build.ps1
+++ b/.github/workflows/build.ps1
@@ -7,8 +7,10 @@ echo "::endgroup::"
 
 echo "::group::Install wheel"
   $test_dep = python .\print_deps.py $env:MB_PYTHON_VERSION ${env:REPO_DIR} -p test
+  echo "test_dep: $test_dep"
   pip install @($test_dep.split())
   pip install --find-links ./wheelhouse --pre nipy
+  pip list | findstr "numpy"
 echo "::endgroup::"
 
 echo "::group::Test wheel"
diff --git a/.github/workflows/build.sh b/.github/workflows/build.sh
index 1716cb1..b691e98 100755
--- a/.github/workflows/build.sh
+++ b/.github/workflows/build.sh
@@ -2,8 +2,7 @@
 # Build on Mac or Linux.
 if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
   # curl from brew requires zstd, use system curl
-  # if php is installed, brew tries to reinstall these after installing openblas
-  brew remove --ignore-dependencies curl php
+  brew remove --ignore-dependencies curl
 
   brew install pkg-config openblas
 
@@ -15,10 +14,12 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
 fi
 
 echo "::group::Install a virtualenv"
+  echo "PLAT: $PLAT; uname: $(uname -m)"
   source multibuild/common_utils.sh
   source multibuild/travis_steps.sh
   before_install
   export TEST_DEPENDS=$(python ./print_deps.py ${MB_PYTHON_VERSION} ${REPO_DIR} -p test)
+  echo "TEST_DEPENDS: $TEST_DEPENDS"
 echo "::endgroup::"
 
 echo "::group::Build wheel"
diff --git a/.github/workflows/wheels-linux.yml b/.github/workflows/wheels-linux.yml
index d05f888..f944ca8 100644
--- a/.github/workflows/wheels-linux.yml
+++ b/.github/workflows/wheels-linux.yml
@@ -26,6 +26,7 @@ jobs:
           "3.10",
           "3.11",
           "3.12",
+          "3.13",
         ]
         mb-ml-libc: [ "manylinux" ]
         mb-ml-ver: [ 2014, "_2_28" ]
@@ -42,6 +43,9 @@ jobs:
           - python: "3.12"
             mb-ml-libc: "musllinux"
             mb-ml-ver: "_1_1"
+          - python: "3.13"
+            mb-ml-libc: "musllinux"
+            mb-ml-ver: "_1_1"
     env:
       BUILD_COMMIT: ${{ inputs.build-commit }}
       MB_PYTHON_VERSION: ${{ matrix.python }}
@@ -51,14 +55,15 @@ jobs:
       - uses: actions/checkout@v4
         with:
           submodules: true
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python }}
+          allow-prereleases: true
       - name: Build Wheel
         run: .github/workflows/build.sh
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
-          name: ${{ inputs.artifacts-name }}
+          name: ${{ inputs.artifacts-name }}-${{ matrix.mb-ml-libc }}${{ matrix.mb-ml-ver }}-${{ matrix.python }}
           path: wheelhouse/*.whl
       # Uncomment to get SSH access for testing
       # - name: Setup tmate session
diff --git a/.github/workflows/wheels-macos.yml b/.github/workflows/wheels-macos.yml
index 9fb731e..13229df 100644
--- a/.github/workflows/wheels-macos.yml
+++ b/.github/workflows/wheels-macos.yml
@@ -16,7 +16,7 @@ env:
 jobs:
   build:
     name: ${{ matrix.python }} ${{ matrix.platform }}
-    runs-on: "macos-latest"
+    runs-on: "macos-13"
     strategy:
       fail-fast: false
       matrix:
@@ -26,6 +26,7 @@ jobs:
           "3.10",
           "3.11",
           "3.12",
+          "3.13",
         ]
         platform: [ "x86_64", "arm64" ]
     env:
@@ -37,14 +38,14 @@ jobs:
       - uses: actions/checkout@v4
         with:
           submodules: true
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: "3.x"
       - name: Build Wheel
         run: .github/workflows/build.sh
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
-          name: ${{ inputs.artifacts-name }}
+          name: ${{ inputs.artifacts-name }}-${{ matrix.platform }}-${{ matrix.python }}
           path: wheelhouse/*.whl
       # Uncomment to get SSH access for testing
       # - name: Setup tmate session
diff --git a/.github/workflows/wheels-windows.yml b/.github/workflows/wheels-windows.yml
index 32744a0..f2f0424 100644
--- a/.github/workflows/wheels-windows.yml
+++ b/.github/workflows/wheels-windows.yml
@@ -26,6 +26,7 @@ jobs:
           "3.10",
           "3.11",
           "3.12",
+          "3.13",
         ]
     env:
       BUILD_COMMIT: ${{ inputs.build-commit }}
@@ -34,14 +35,15 @@ jobs:
       - uses: actions/checkout@v4
         with:
           submodules: true
-      - uses: actions/setup-python@v4
+      - uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python }}
+          allow-prereleases: true
       - name: Build Wheel
         run: .github/workflows/build.ps1
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
-          name: ${{ inputs.artifacts-name }}
+          name: ${{ inputs.artifacts-name }}-${{ matrix.python }}
           path: wheelhouse/*.whl
       # Uncomment to get SSH access for testing
       # - name: Setup tmate session
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 4d52a26..fa9bcfe 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -11,19 +11,19 @@ jobs:
     uses: ./.github/workflows/wheels-macos.yml
     with:
       build-commit: "HEAD"
-      artifacts-name: "wheels"
+      artifacts-name: "macos-wheels"
 
   linux:
     uses: ./.github/workflows/wheels-linux.yml
     with:
       build-commit: "HEAD"
-      artifacts-name: "wheels"
+      artifacts-name: "manylinux-wheels"
 
   windows:
     uses: ./.github/workflows/wheels-windows.yml
     with:
       build-commit: "HEAD"
-      artifacts-name: "wheels"
+      artifacts-name: "windows-wheels"
 
   release:
     name: Create Release
@@ -31,9 +31,10 @@ jobs:
     if: "startsWith(github.ref, 'refs/tags/')"
     needs: [macos, linux, windows]
     steps:
-      - uses: actions/download-artifact@v3
+      - uses: actions/download-artifact@v4
         with:
-          name: wheels
+          pattern: "*-wheels-*.zip"
+          merge-multiple: true
       - name: Upload Release
         uses: fnkr/github-action-ghr@v1.3
         env:
diff --git a/.gitignore b/.gitignore
index 977482c..e54b5b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 tmp_for_test/
 working/
 build/
+_wheels/
diff --git a/README.rst b/README.rst
index 6eb36fc..91de556 100644
--- a/README.rst
+++ b/README.rst
@@ -2,19 +2,20 @@
 Building and uploading nipy wheels
 ##################################
 
-We automate wheel building using this custom github
-repository that builds on the travis-ci macOS machines and
-the travis-ci Linux machines.
-
-The travis-ci interface for the builds is
-https://travis-ci.org/MacPython/nipy-wheels
-
-Appveyor interface at
-https://ci.appveyor.com/project/matthew-brett/nipy-wheels
+We automate wheel building using this custom github repository that builds on
+the Github Actions machines.
 
 The driving github repository is
 https://github.com/MacPython/nipy-wheels
 
+Things to configure
+===================
+
+Update the `nipy` submodule commit as below.
+
+Consider changing the Numpy versions used for the build, configured in
+`print_deps.py`.
+
 How it works
 ============
 
@@ -27,48 +28,23 @@ The wheel-building repository:
   ``auditwheel`` copy the required dynamic libraries into
   the wheel and relinks the extension modules against the
   copied libraries;
-* uploads the built wheels to http://anaconda.org/nipy/nipy
+* uploads the built wheels as Github Actions artifacts.
 
 The resulting wheels are therefore self-contained and do not need any external
 dynamic libraries apart from those provided as standard by macOS / Linux as
 defined by the manylinux1 standard.
 
-The ``.travis.yml`` file in this repository has a line
-containing the API key for the Anaconda.org organization
-encrypted with an RSA key that is unique to the repository
-- see http://docs.travis-ci.com/user/encryption-keys.
-This encrypted key gives the travis build permission to
-upload to the Anaconda.org directory pointed to by
-https://anaconda.org/nipy/nipy .
-
 Triggering a build
 ==================
 
-You will likely want to edit the ``.travis.yml`` and ``appveyor.yml`` files to
-specify the ``BUILD_COMMIT`` before triggering a build - see below.
+Update the `nipy` submodule to the commit you want to build, and commit that
+change to this repo.
 
 You will need write permission to the github repository to trigger new builds
-on the travis-ci interface.  Contact us on the mailing list if you need this.
+on the Actions interface.  Contact us on the mailing list if you need this.
 
-You can trigger a build by:
-
-* making a commit to the ``nipy-wheels`` repository (e.g. with ``git commit
-  --allow-empty``); or
-* clicking on the circular arrow icon towards the top right of the travis-ci
-  page, to rerun the previous build.
-
-In general, it is better to trigger a build with a commit, because this makes
-a new set of build products and logs, keeping the old ones for reference.
-Keeping the old build logs helps us keep track of previous problems and
-successful builds.
-
-Which nipy commit does the repository build?
-============================================
-
-The ``nipy-wheels`` repository will build the commit specified in the
-``BUILD_COMMIT`` at the top of the ``.travis.yml`` and ``appveyor.yml`` files.
-This can be any naming of a commit, including branch name, tag name or commit
-hash.
+You can trigger a build by making a commit to the ``nipy-wheels`` repository
+(e.g. with ``git commit --allow-empty``); or
 
 Uploading the built wheels to pypi
 ==================================
@@ -78,16 +54,15 @@ and then upload them manually to pypi, or by using twine_.
 
 To download, use something like::
 
-    python tools/download-wheels.py 0.5.0 --staging-url=https://anaconda.org/nipy/nipy --prefix=nipy -w wheelhouse
-
-where `0.5.0` is the release version.
+    gh run download --pattern "*-wheels-*" --dir _wheels
+    cp _wheels/*/*.whl wheelhouse
 
 You may want to add the `sdist` to the `wheelhouse`.  Build, copy with::
 
     (cd nipy && make source-release)
     cp nipy/dist/*.tar.gz wheelhouse
 
-Then upload everything with::
+Then upload everything with e.g.::
 
     twine upload --sign wheelhouse/nipy-0.5.0-*
 
diff --git a/multibuild b/multibuild
index 18d8903..f876f1d 160000
--- a/multibuild
+++ b/multibuild
@@ -1 +1 @@
-Subproject commit 18d8903b4cb1607819d4c144c4b5e6a72bf5a695
+Subproject commit f876f1d896de406db13900e9768c4fb496957681
diff --git a/nipy b/nipy
index b3d202f..e256e70 160000
--- a/nipy
+++ b/nipy
@@ -1 +1 @@
-Subproject commit b3d202f7aa46542804169d5532fdd8c3f34fb836
+Subproject commit e256e7016ca01662d20501cadb3cc6cdf4275055
diff --git a/patches/setup_egg.py b/patches/setup_egg.py
deleted file mode 100644
index 778fe11..0000000
--- a/patches/setup_egg.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python
-# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
-# vi: set ft=python sts=4 ts=4 sw=4 et:
-"""Wrapper to run setup.py using setuptools."""
-
-from setuptools import setup
-
-################################################################################
-# Call the setup.py script, injecting the setuptools-specific arguments.
-
-extra_setuptools_args = dict(
-                            tests_require=['nose'],
-                            test_suite='nose.collector',
-                            zip_safe=False,
-                            )
-
-
-if __name__ == '__main__':
-    exec(open('setup.py', 'rt').read(), dict(__name__='__main__',
-                                             extra_setuptools_args=extra_setuptools_args))
diff --git a/print_deps.py b/print_deps.py
index 08f9dc7..b0e1ee8 100755
--- a/print_deps.py
+++ b/print_deps.py
@@ -12,6 +12,9 @@
     import tomli as tlib
 
 
+IS_MUSL = os.environ.get('MB_ML_LIBC') == 'musllinux'
+
+
 def get_phase_requirements(repo_path, phase='build'):
     toml = (Path(repo_path) / 'pyproject.toml').read_text()
     config = tlib.loads(toml)
@@ -28,15 +31,15 @@ def get_phase_requirements(repo_path, phase='build'):
 def get_numpy_requirement(py_ver):
     major, minor, *_ = py_ver.split('.')
     assert major == "3"
-    musl = os.environ.get('MB_ML_LIBC') == 'musllinux'
-    # musllinux wheels started at 1.25.0
-    np_version = "1.22.0" if not musl else '1.25.0'
     minor = int(minor)
-    if minor >= 12:
-        np_version = "1.26.0"
-    elif minor >= 11 and not musl:
-        np_version = "1.23.2"
-    return np_version
+    if minor <= 8:
+        if IS_MUSL:
+            raise RuntimeError("MUSL doesn't have 3.8 wheels")
+        # SPEC0-minimum as of Dec 23, 2023
+        return "1.22.0"
+    if minor == 9:
+        return "2.0.2"
+    return "2.1.2"
 
 
 def get_parser():
