diff --git a/share/yashchiki/utils/fetch_sources.sh b/share/yashchiki/utils/fetch_sources.sh
new file mode 100755
index 0000000000000000000000000000000000000000..82cc45a6bfa698055991c7e9e542d6c5e3708cff
--- /dev/null
+++ b/share/yashchiki/utils/fetch_sources.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+set -euo pipefail
+shopt -s inherit_errexit
+
+# TODO:
+# expects active environment
+
+(
+    specfile="./env_specfile.yaml";
+    # TODO: expects loaded env
+    spack spec -y > "${specfile}"
+) && echo "done." || (echo "FAILED."; exit 1)
+
+fetch_specfiles=()
+fetch_specfiles_cache=()
+
+# spack fetch doesn't like proper multi-doc separators (---)
+csplit -z --prefix=env_specfile_split --suffix-format=%02d.yaml ./env_specfile.yaml /---/ '{*}'
+for split_specfile in ./env_specfile_split*.yaml; do
+    fetch_specfiles+=( ${split_specfile} )
+done
+# multi-doc style for "normal" yaml parsing
+fetch_specfiles_cache+=( "./env_specfile.yaml" )
+
+
+missing_paths=()
+if [ -n "${CACHE_SOURCE_TYPE:-}" ]; then
+    if [ ${CACHE_SOURCE_TYPE} != "oci" ]; then
+        echo "Unknown cache type"
+        exit 1
+    fi
+    for fetch_specfile in "${fetch_specfiles_cache[@]}"; do
+        raw_paths=$(PATH=${MY_SPACK_FOLDER}/bin:$PATH ${MY_SPACK_PYTHON} ${YASHCHIKI_HOME}/lib/yashchiki/specfile_storage_path.py ${fetch_specfile})
+        fetch_paths=(${raw_paths})
+        pushd ${YASHCHIKI_CACHES_ROOT}/download_cache
+        for fetch_path in "${fetch_paths[@]}"; do
+            # FIXME: gitlab env vars!
+            oras pull ${HARBOR_HOST}/${HARBOR_PROJECT}/source_cache:$(basename ${fetch_path}) 2>&1 && ret=$? || ret=$?
+            if [ ${ret} -ne 0 ]; then
+                missing_paths+=( "${fetch_path}" )
+            fi
+        done
+        popd
+    done
+    echo "Missing source cache entries: ${missing_paths[@]}"
+fi