From d2de0e485e6969d8e074cf571251bebb7d16af4d Mon Sep 17 00:00:00 2001 From: Valentin Volkl Date: Sun, 8 Mar 2026 13:42:50 +0100 Subject: [PATCH 1/8] feat: use GitHub Actions cache to persist the CernVM-FS cache Co-authored-by: Wouter Deconinck --- .github/workflows/cache.yml | 321 ++++++++++++++++++++++++++++++++++++ README.md | 3 +- action.yml | 25 ++- setup-cvmfs.sh | 22 +++ 4 files changed, 369 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/cache.yml diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml new file mode 100644 index 0000000..b564222 --- /dev/null +++ b/.github/workflows/cache.yml @@ -0,0 +1,321 @@ +name: with actions_cache +on: + push: + pull_request: + schedule: + - cron: '21 6 * * *' + +env: + CVMFS_CACHE_BASE: /opt/cvmfs-cache + +jobs: + populate-cache: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - uses: ./ + with: + actions_cache: 'true' + cvmfs_cache_base: ${{ env.CVMFS_CACHE_BASE }} + cvmfs_repositories: 'sft.cern.ch' + - name: Diagnose workspace and cache base access before first access + run: | + cache_parent=$(dirname "${{ env.CVMFS_CACHE_BASE }}") + echo "### Compare workspace and cache path ancestry ###" + namei -l "${{ github.workspace }}" || true + namei -l "${cache_parent}" || true + namei -l "${{ env.CVMFS_CACHE_BASE }}" || true + echo "### Check action-prepared cache base permissions ###" + id + getent passwd cvmfs + ls -ld "${{ github.workspace }}" "${cache_parent}" "${{ env.CVMFS_CACHE_BASE }}" + test -r "${{ env.CVMFS_CACHE_BASE }}" + test -w "${{ env.CVMFS_CACHE_BASE }}" + sudo -u cvmfs test -w "${{ env.CVMFS_CACHE_BASE }}" + - name: Populate CernVM-FS cache + run: | + echo "### Dump default.local ###" + cat /etc/cvmfs/default.local + echo "### Check configured cache base ###" + grep "CVMFS_CACHE_BASE='${{ env.CVMFS_CACHE_BASE }}'" /etc/cvmfs/default.local + echo "### Service and mount state before first repository access ###" + sudo cvmfs_config chksetup || true + sudo systemctl status autofs --no-pager || true + mount | grep -E "autofs|cvmfs" || true + findmnt /cvmfs || true + findmnt /cvmfs/sft.cern.ch || true + echo "### Probe before first repository access ###" + set +e + sudo cvmfs_config probe + pre_probe_rc=$? + set -e + printf 'Pre-access probe exit code: %s\n' "${pre_probe_rc}" + if [ "${pre_probe_rc}" -ne 0 ]; then + echo "::warning::cvmfs_config probe failed before first repository access" + fi + echo "### Exercise /cvmfs/sft.cern.ch ###" + ls /cvmfs/sft.cern.ch + echo "### Check lcg/lastUpdate before the deeper cache exercise ###" + show_exact_target_io_diagnostics() { + echo "### journalctl: autofs unit ###" + sudo journalctl -u autofs --no-pager -n 100 || true + echo "### journalctl: filtered cvmfs/autofs/I-O lines ###" + sudo journalctl --no-pager -n 400 | grep -Ei 'cvmfs|autofs|Input/output|I/O error|sft\.cern\.ch' | tail -n 120 || true + if [ -f /var/log/syslog ]; then + echo "### /var/log/syslog: filtered cvmfs/autofs/I-O lines ###" + sudo tail -n 400 /var/log/syslog | grep -Ei 'cvmfs|autofs|Input/output|I/O error|sft\.cern\.ch' | tail -n 120 || true + fi + } + exact_update_marker=/cvmfs/sft.cern.ch/lcg/lastUpdate + if ! ls "${exact_update_marker}"; then + echo "::warning::Exact lcg/lastUpdate access failed; dumping CVMFS/autofs diagnostics before fallback" + show_exact_target_io_diagnostics + fi + echo "### Resolve a Bazel target to exercise broader cache content ###" + exact_bazel_bin=/cvmfs/sft.cern.ch/lcg/contrib/bazel/Linux-x86_64//lib/bazel/bin/bazel + if ! ls "${exact_bazel_bin}"; then + echo "::warning::Exact bazel access failed; dumping CVMFS/autofs diagnostics before fallback" + show_exact_target_io_diagnostics + fi + bazel_bin="${exact_bazel_bin}" + if [ ! -e "${bazel_bin}" ]; then + bazel_bin=$({ + find /cvmfs/sft.cern.ch/lcg/contrib/bazel -path '*/Linux-x86_64*/lib/bazel/bin/bazel' -print 2>/dev/null + find /cvmfs/sft.cern.ch/lcg/contrib/bazel -path '*/lib/bazel/bin/bazel' -print 2>/dev/null + } | sort -u | tail -n 1) + fi + if [ -z "${bazel_bin}" ]; then + echo "::error::Could not find any fallback bazel binary under /cvmfs/sft.cern.ch/lcg/contrib/bazel" + exit 1 + fi + bazel_bin_dir=${bazel_bin%/bazel} + bazel_root_dir=${bazel_bin_dir%/bin} + printf 'Selected bazel binary: %s\n' "${bazel_bin}" + echo "### Try the exact problematic ROOT libTree.so path without making it mandatory ###" + exact_root_lib=/cvmfs/sft.cern.ch/lcg/releases/ROOT/6.36.02-1f134/x86_64-ubuntu2404-gcc13-opt/lib/libTree.so + root_lib_for_read= + if ls "${exact_root_lib}"; then + root_lib_for_read="${exact_root_lib}" + else + echo "::warning::Exact ROOT libTree.so access failed; dumping CVMFS/autofs diagnostics and continuing without making it required" + show_exact_target_io_diagnostics + fi + echo "### Read the checked paths and nearby bazel files to populate more cached objects ###" + { + if [ -n "${root_lib_for_read}" ]; then + printf '%s\n' "${root_lib_for_read}" + fi + if [ -e "${exact_update_marker}" ]; then + printf '%s\n' "${exact_update_marker}" + fi + printf '%s\n' "${bazel_bin}" + find "${bazel_bin_dir}" -maxdepth 1 \( -type f -o -type l \) | sort + find "${bazel_root_dir}" -maxdepth 1 \( -type f -o -type l \) | sort + } | awk '!seen[$0]++' > "${RUNNER_TEMP}/bazel-files.txt" + test -s "${RUNNER_TEMP}/bazel-files.txt" + grep -F '/bin/bazel' "${RUNNER_TEMP}/bazel-files.txt" + while IFS= read -r path; do + cat "${path}" > /dev/null + done < "${RUNNER_TEMP}/bazel-files.txt" + printf 'Read %s bazel-related entries from %s\n' "$(wc -l < "${RUNNER_TEMP}/bazel-files.txt")" "${bazel_root_dir}" + echo "### Probe after first repository access ###" + set +e + sudo cvmfs_config probe + post_probe_rc=$? + set -e + printf 'Post-access probe exit code: %s\n' "${post_probe_rc}" + if [ "${pre_probe_rc}" -ne 0 ] && [ "${post_probe_rc}" -eq 0 ]; then + echo "Probe succeeded after first repository access" + fi + echo "### Service and mount state after first repository access ###" + mount | grep -E "autofs|cvmfs" || true + findmnt /cvmfs || true + findmnt /cvmfs/sft.cern.ch || true + sudo cvmfs_config status || true + echo "### Assert cache was populated ###" + sudo test -e "${{ env.CVMFS_CACHE_BASE }}/shared/cachedb" + populated_object=$(sudo find "${{ env.CVMFS_CACHE_BASE }}/shared" -mindepth 2 -maxdepth 2 -type f -path "${{ env.CVMFS_CACHE_BASE }}/shared/[0-9a-f][0-9a-f]/*" | head -n 1) + test -n "${populated_object}" + printf 'Populated cache object: %s\n' "${populated_object}" + echo "### Show selected cache entries ###" + sudo find "${{ env.CVMFS_CACHE_BASE }}/shared" -maxdepth 2 \( -name cachedb -o -name '[0-9a-f][0-9a-f]' \) | sort | head -n 40 + if [ "${post_probe_rc}" -ne 0 ]; then + echo "::error::cvmfs_config probe still failed after first repository access" + exit "${post_probe_rc}" + fi + - name: Make CernVM-FS cache readable for actions/cache post-job save + if: ${{ always() }} + run: | + if [ -d "${{ env.CVMFS_CACHE_BASE }}" ]; then + sudo chmod -R a+rwX "${{ env.CVMFS_CACHE_BASE }}" + sudo find "${{ env.CVMFS_CACHE_BASE }}" -type d -exec chmod a+rwx {} + + fi + + reuse-cache: + needs: populate-cache + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - uses: ./ + with: + actions_cache: 'true' + cvmfs_cache_base: ${{ env.CVMFS_CACHE_BASE }} + cvmfs_repositories: 'sft.cern.ch' + - name: Diagnose workspace and cache base access before reuse + run: | + cache_parent=$(dirname "${{ env.CVMFS_CACHE_BASE }}") + echo "### Compare workspace and cache path ancestry ###" + namei -l "${{ github.workspace }}" || true + namei -l "${cache_parent}" || true + namei -l "${{ env.CVMFS_CACHE_BASE }}" || true + echo "### Check action-prepared cache base permissions ###" + id + getent passwd cvmfs + ls -ld "${{ github.workspace }}" "${cache_parent}" "${{ env.CVMFS_CACHE_BASE }}" + test -r "${{ env.CVMFS_CACHE_BASE }}" + test -w "${{ env.CVMFS_CACHE_BASE }}" + sudo -u cvmfs test -w "${{ env.CVMFS_CACHE_BASE }}" + echo "### Check autofs / cvmfs setup ###" + sudo cvmfs_config chksetup || true + sudo systemctl is-active autofs || true + sudo systemctl status autofs --no-pager || true + findmnt /cvmfs || true + findmnt /cvmfs/sft.cern.ch || true + - name: Assert restored CernVM-FS cache contents + run: | + echo "### Check restored cache before first repository access ###" + sudo test -e "${{ env.CVMFS_CACHE_BASE }}/shared/cachedb" + restored_object=$(sudo find "${{ env.CVMFS_CACHE_BASE }}/shared" -mindepth 2 -maxdepth 2 -type f -path "${{ env.CVMFS_CACHE_BASE }}/shared/[0-9a-f][0-9a-f]/*" | head -n 1) + if [ -z "${restored_object}" ]; then + echo "Expected restored cache content under the persistent hash directories before first CVMFS access" + sudo find "${{ env.CVMFS_CACHE_BASE }}/shared" -maxdepth 2 \( -name cachedb -o -name '[0-9a-f][0-9a-f]' \) | sort | head -n 80 + exit 1 + fi + printf 'Restored cache object: %s\n' "${restored_object}" + - name: Reuse populated CernVM-FS cache + run: | + echo "### Dump default.local ###" + cat /etc/cvmfs/default.local + echo "### Try cvmfs_config probe ###" + if ! sudo cvmfs_config probe; then + echo "::warning::Initial cvmfs_config probe failed; dumping diagnostics, restarting autofs, and retrying once" + sudo systemctl status autofs --no-pager || true + findmnt /cvmfs || true + findmnt /cvmfs/sft.cern.ch || true + sudo systemctl restart autofs + sleep 2 + sudo systemctl is-active autofs + sudo cvmfs_config probe + fi + show_cvmfs_talk_metrics() { + label="$1" + metrics_file="${RUNNER_TEMP}/cvmfs-${label}.prom" + echo "### cvmfs_talk metrics (${label}) ###" + sudo cvmfs_talk -i sft.cern.ch metrics prometheus > "${metrics_file}" || return 0 + grep -Ei 'cvmfs_.*(cache|download|hit|rx)' "${metrics_file}" | head -n 40 || true + } + echo "### Reuse /cvmfs/sft.cern.ch ###" + ls /cvmfs/sft.cern.ch + echo "### Resolve the lcg/lastUpdate and bazel targets used for targeted reuse ###" + show_exact_target_io_diagnostics() { + echo "### journalctl: autofs unit ###" + sudo journalctl -u autofs --no-pager -n 100 || true + echo "### journalctl: filtered cvmfs/autofs/I-O lines ###" + sudo journalctl --no-pager -n 400 | grep -Ei 'cvmfs|autofs|Input/output|I/O error|sft\.cern\.ch' | tail -n 120 || true + if [ -f /var/log/syslog ]; then + echo "### /var/log/syslog: filtered cvmfs/autofs/I-O lines ###" + sudo tail -n 400 /var/log/syslog | grep -Ei 'cvmfs|autofs|Input/output|I/O error|sft\.cern\.ch' | tail -n 120 || true + fi + } + exact_update_marker=/cvmfs/sft.cern.ch/lcg/lastUpdate + if ! ls "${exact_update_marker}"; then + echo "::warning::Exact lcg/lastUpdate access failed; dumping CVMFS/autofs diagnostics before fallback" + show_exact_target_io_diagnostics + fi + exact_bazel_bin=/cvmfs/sft.cern.ch/lcg/contrib/bazel/Linux-x86_64//lib/bazel/bin/bazel + if ! ls "${exact_bazel_bin}"; then + echo "::warning::Exact bazel access failed; dumping CVMFS/autofs diagnostics before fallback" + show_exact_target_io_diagnostics + fi + bazel_bin="${exact_bazel_bin}" + if [ ! -e "${bazel_bin}" ]; then + bazel_bin=$({ + find /cvmfs/sft.cern.ch/lcg/contrib/bazel -path '*/Linux-x86_64*/lib/bazel/bin/bazel' -print 2>/dev/null + find /cvmfs/sft.cern.ch/lcg/contrib/bazel -path '*/lib/bazel/bin/bazel' -print 2>/dev/null + } | sort -u | tail -n 1) + fi + if [ -z "${bazel_bin}" ]; then + echo "::error::Could not find any fallback bazel binary under /cvmfs/sft.cern.ch/lcg/contrib/bazel" + exit 1 + fi + bazel_bin_dir=${bazel_bin%/bazel} + bazel_root_dir=${bazel_bin_dir%/bin} + printf 'Selected bazel binary: %s\n' "${bazel_bin}" + echo "### Try the exact problematic ROOT libTree.so path without making it mandatory ###" + exact_root_lib=/cvmfs/sft.cern.ch/lcg/releases/ROOT/6.36.02-1f134/x86_64-ubuntu2404-gcc13-opt/lib/libTree.so + root_lib_for_read= + if ls "${exact_root_lib}"; then + root_lib_for_read="${exact_root_lib}" + else + echo "::warning::Exact ROOT libTree.so access failed; dumping CVMFS/autofs diagnostics and continuing without making it required" + show_exact_target_io_diagnostics + fi + echo "### Inspect restored cache state via cvmfs_talk before targeted reuse ###" + sudo cvmfs_talk -i sft.cern.ch cache instance || true + sudo cvmfs_talk -i sft.cern.ch cache size || true + sudo cvmfs_talk -i sft.cern.ch cache list > "${RUNNER_TEMP}/cache-list-before.txt" + if [ -e "${exact_update_marker}" ]; then + if grep -F 'lastUpdate' "${RUNNER_TEMP}/cache-list-before.txt"; then + echo "lastUpdate already appears in cache list before the targeted reuse read" + else + echo "::notice::lastUpdate not yet visible in cache list before the targeted reuse read" + fi + else + echo "::notice::Skipping cache-list check for lcg/lastUpdate because the exact path is not currently available" + fi + if grep -F 'bazel' "${RUNNER_TEMP}/cache-list-before.txt"; then + echo "bazel already appears in cache list before the targeted reuse read" + else + echo "::notice::bazel not yet visible in cache list before the targeted reuse read" + fi + show_cvmfs_talk_metrics before-targeted-reuse + echo "### Re-read the checked paths and nearby bazel files to confirm cache reuse on richer content ###" + { + if [ -n "${root_lib_for_read}" ]; then + printf '%s\n' "${root_lib_for_read}" + fi + if [ -e "${exact_update_marker}" ]; then + printf '%s\n' "${exact_update_marker}" + fi + printf '%s\n' "${bazel_bin}" + find "${bazel_bin_dir}" -maxdepth 1 \( -type f -o -type l \) | sort + find "${bazel_root_dir}" -maxdepth 1 \( -type f -o -type l \) | sort + } | awk '!seen[$0]++' > "${RUNNER_TEMP}/bazel-files.txt" + test -s "${RUNNER_TEMP}/bazel-files.txt" + grep -F '/bin/bazel' "${RUNNER_TEMP}/bazel-files.txt" + while IFS= read -r path; do + cat "${path}" > /dev/null + done < "${RUNNER_TEMP}/bazel-files.txt" + printf 'Re-read %s bazel-related entries from %s\n' "$(wc -l < "${RUNNER_TEMP}/bazel-files.txt")" "${bazel_root_dir}" + echo "### Require checked paths in cvmfs_talk cache list during reuse ###" + sudo cvmfs_talk -i sft.cern.ch cache list > "${RUNNER_TEMP}/cache-list-after.txt" + if [ -e "${exact_update_marker}" ] && ! grep -F 'lastUpdate' "${RUNNER_TEMP}/cache-list-after.txt"; then + echo "::error::lastUpdate did not appear in cvmfs_talk cache list after the targeted reuse read" + sed -n '1,120p' "${RUNNER_TEMP}/cache-list-after.txt" + exit 1 + fi + if ! grep -F 'bazel' "${RUNNER_TEMP}/cache-list-after.txt"; then + echo "::error::bazel did not appear in cvmfs_talk cache list after the targeted reuse read" + sed -n '1,120p' "${RUNNER_TEMP}/cache-list-after.txt" + exit 1 + fi + show_cvmfs_talk_metrics after-targeted-reuse + echo "### Show selected cache entries ###" + sudo find "${{ env.CVMFS_CACHE_BASE }}/shared" -maxdepth 2 \( -name cachedb -o -name '[0-9a-f][0-9a-f]' \) | sort | head -n 40 + - name: Make CernVM-FS cache readable for actions/cache post-job save + if: ${{ always() }} + run: | + if [ -d "${{ env.CVMFS_CACHE_BASE }}" ]; then + sudo chmod -R a+rwX "${{ env.CVMFS_CACHE_BASE }}" + sudo find "${{ env.CVMFS_CACHE_BASE }}" -type d -exec chmod a+rwx {} + + fi \ No newline at end of file diff --git a/README.md b/README.md index 2c0d858..6f5fc10 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ By default `*.cern.ch, *.egi.eu, *.opensciencegrid.org *.hsf.org` repositories ## Optional Parameters The following parameters are supported: +- `actions_cache`: Enable a GitHub Actions cache for the persistent contents of `cvmfs_cache_base`. Requires `cvmfs_cache_base` to be set. With the default shared cache, it caches `shared/cachedb` plus `shared/00`...`shared/ff`; with `cvmfs_shared_cache=no`, it caches `cachedb` plus `00`...`ff` directly under `cvmfs_cache_base`. - `cvmfs_alien_cache`: If set, use an alien cache at the given location. - `cvmfs_alt_root_path`: If set to yes, use alternative root catalog path. Only required for fixed catalogs (tag / hash) under the alternative path. - `cvmfs_authz_helper`: Full path to an authz helper, overwrites the helper hint in the catalog. @@ -27,7 +28,7 @@ The following parameters are supported: - `cvmfs_auto_update`: If set to no, disables the automatic update of file catalogs. - `cvmfs_backoff_init`: Seconds for the maximum initial backoff when retrying to download data. - `cvmfs_backoff_max`: Maximum backoff in seconds when retrying to download data. -- `cvmfs_cache_base`: Location (directory) of the CernVM-FS cache. +- `cvmfs_cache_base`: Location (directory) of the CernVM-FS cache. The `cvmfs` user must be able to traverse all parent directories and write this directory; on GitHub-hosted runners prefer a path under `/tmp` over the GitHub workspace path. - `cvmfs_catalog_watermark`: Try to release pinned catalogs when their number surpasses the given watermark. Defaults to 1/4 CVMFS_NFILES; explicitly set by shrinkwrap. - `cvmfs_check_permissions`: If set to no, disable checking of file ownership and permissions (open all files). - `cvmfs_claim_ownership`: If set to yes, allows CernVM-FS to claim ownership of files and directories. diff --git a/action.yml b/action.yml index d4d3a66..df05097 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,10 @@ inputs: description: 'If set to yes, cache APT package lists in addition to .deb packages. This speeds up the action, but may require manual intervention to clear the cache when gh actions runner images are updated' required: false default: 'no' + actions_cache: + description: 'Enable a GitHub Actions cache for persistent CernVM-FS cache contents under cvmfs_cache_base. With the default shared cache, cachedb and object directories live under the shared subdirectory.' + required: false + default: 'false' cvmfs_alien_cache: description: 'If set, use an alien cache at the given location.' required: false @@ -41,7 +45,7 @@ inputs: required: false default: '' cvmfs_cache_base: - description: 'Location (directory) of the CernVM-FS cache.' + description: 'Location (directory) of the CernVM-FS cache. The cvmfs user must be able to traverse all parent directories and write this directory; on GitHub-hosted runners prefer a path such as /opt/cvmfs-cache so the action can prepare it for both the cvmfs user and the runner user.' required: false default: '' cvmfs_catalog_watermark: @@ -360,6 +364,25 @@ runs: key: cvmfs-apt-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }} path: | ${{ inputs.apt_cache }} + - if: ${{ inputs.cvmfs_cache_base != '' }} + run: | + sudo mkdir -p "${{ inputs.cvmfs_cache_base }}" + sudo chmod a+rwx "${{ inputs.cvmfs_cache_base }}" + shell: bash + - uses: actions/cache@v5 + if: ${{ inputs.actions_cache == 'true' && inputs.cvmfs_cache_base != '' && inputs.cvmfs_shared_cache != 'no' }} + with: + key: cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }} + path: | + ${{ inputs.cvmfs_cache_base }}/shared/cachedb + ${{ inputs.cvmfs_cache_base }}/shared/[0-9a-f][0-9a-f] + - uses: actions/cache@v5 + if: ${{ inputs.actions_cache == 'true' && inputs.cvmfs_cache_base != '' && inputs.cvmfs_shared_cache == 'no' }} + with: + key: cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }} + path: | + ${{ inputs.cvmfs_cache_base }}/cachedb + ${{ inputs.cvmfs_cache_base }}/[0-9a-f][0-9a-f] - run: | ${{ github.action_path }}/setup-cvmfs.sh shell: bash diff --git a/setup-cvmfs.sh b/setup-cvmfs.sh index 749d2ee..62fa007 100755 --- a/setup-cvmfs.sh +++ b/setup-cvmfs.sh @@ -48,6 +48,28 @@ if [ "$(uname)" == "Linux" ]; then fi echo "::endgroup::" fi + if [ -n "${CVMFS_CACHE_BASE}" ]; then + echo "::group::Preparing cvmfs cache base" + mkdir -p "${CVMFS_CACHE_BASE}" + sudo chown -R cvmfs:root "${CVMFS_CACHE_BASE}" + sudo chmod -R a+rwX "${CVMFS_CACHE_BASE}" + sudo find "${CVMFS_CACHE_BASE}" -type d -exec chmod a+rwx {} + + id cvmfs + sudo -u cvmfs id + ls -ld "${CVMFS_CACHE_BASE}" + sudo -u cvmfs test -w "${CVMFS_CACHE_BASE}" + sudo -u cvmfs env CVMFS_CACHE_BASE="${CVMFS_CACHE_BASE}" bash <<'EOF' +set -euo pipefail +probe_file="${CVMFS_CACHE_BASE}/.cvmfs-write-probe" +printf 'probe\n' > "${probe_file}" +rm -f "${probe_file}" +EOF + ls -ld "${CVMFS_CACHE_BASE}" + sudo find "${CVMFS_CACHE_BASE}" -mindepth 1 -maxdepth 1 | sort || true + test -r "${CVMFS_CACHE_BASE}" + test -w "${CVMFS_CACHE_BASE}" + echo "::endgroup::" + fi elif [ "$(uname)" == "Darwin" ]; then # Warn about the phasing out of MacOS support for this action echo "warning The CernVM-FS GitHub Action's support for MacOS \ From f7f70a58623e9631a939482869488e74cb355721 Mon Sep 17 00:00:00 2001 From: Valentin Volkl Date: Tue, 17 Mar 2026 09:32:54 +0100 Subject: [PATCH 2/8] set true by default --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index df05097..9ec87da 100644 --- a/action.yml +++ b/action.yml @@ -15,7 +15,7 @@ inputs: actions_cache: description: 'Enable a GitHub Actions cache for persistent CernVM-FS cache contents under cvmfs_cache_base. With the default shared cache, cachedb and object directories live under the shared subdirectory.' required: false - default: 'false' + default: 'true' cvmfs_alien_cache: description: 'If set, use an alien cache at the given location.' required: false From be3f8a82e9aa105c3b6a78ab5f5eb78b4deeb3c1 Mon Sep 17 00:00:00 2001 From: Valentin Volkl Date: Tue, 31 Mar 2026 11:44:12 +0200 Subject: [PATCH 3/8] cvmfs_cache_max_sise, restore_keys, change default --- action.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/action.yml b/action.yml index 9ec87da..71809a1 100644 --- a/action.yml +++ b/action.yml @@ -15,7 +15,11 @@ inputs: actions_cache: description: 'Enable a GitHub Actions cache for persistent CernVM-FS cache contents under cvmfs_cache_base. With the default shared cache, cachedb and object directories live under the shared subdirectory.' required: false - default: 'true' + default: 'no' + cvmfs_cache_max_size: + description: 'Maximum size in MB of the CernVM-FS cache to persist in the GitHub Actions cache. Before saving, the cache is trimmed with cvmfs_talk cleanup. Set to 0 to disable trimming.' + required: false + default: '4096' cvmfs_alien_cache: description: 'If set, use an alien cache at the given location.' required: false @@ -372,14 +376,18 @@ runs: - uses: actions/cache@v5 if: ${{ inputs.actions_cache == 'true' && inputs.cvmfs_cache_base != '' && inputs.cvmfs_shared_cache != 'no' }} with: - key: cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }} + key: cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }}-${{ github.run_id }} + restore-keys: | + cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }}- path: | ${{ inputs.cvmfs_cache_base }}/shared/cachedb ${{ inputs.cvmfs_cache_base }}/shared/[0-9a-f][0-9a-f] - uses: actions/cache@v5 if: ${{ inputs.actions_cache == 'true' && inputs.cvmfs_cache_base != '' && inputs.cvmfs_shared_cache == 'no' }} with: - key: cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }} + key: cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }}-${{ github.run_id }} + restore-keys: | + cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }}- path: | ${{ inputs.cvmfs_cache_base }}/cachedb ${{ inputs.cvmfs_cache_base }}/[0-9a-f][0-9a-f] @@ -470,3 +478,14 @@ runs: CVMFS_UBUNTU_DEB_LOCATION: ${{ inputs.cvmfs_ubuntu_deb_location }} CVMFS_MACOS_PKG_LOCATION: ${{ inputs.cvmfs_macos_pkg_location }} CVMFS_CONFIG_PACKAGE: ${{ inputs.cvmfs_config_package }} + - if: ${{ inputs.actions_cache == 'true' && inputs.cvmfs_cache_base != '' && inputs.cvmfs_cache_max_size != '0' }} + run: | + # Trim the CernVM-FS cache to the configured maximum size + if [ -n "${{ inputs.cvmfs_repositories }}" ]; then + IFS=',' read -ra REPOS <<< "${{ inputs.cvmfs_repositories }}" + for repo in "${REPOS[@]}"; do + repo=$(echo "$repo" | xargs) # trim whitespace + sudo cvmfs_talk -i "$repo" cleanup "${{ inputs.cvmfs_cache_max_size }}" 2>/dev/null || true + done + fi + shell: bash From 8d9601472bd6e49f39adb13e162e42d398ec5487 Mon Sep 17 00:00:00 2001 From: Valentin Volkl Date: Tue, 31 Mar 2026 15:51:41 +0200 Subject: [PATCH 4/8] add default for cache_base --- README.md | 4 ++-- action.yml | 32 ++++++++++++++++++++------------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6f5fc10..9421136 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ By default `*.cern.ch, *.egi.eu, *.opensciencegrid.org *.hsf.org` repositories ## Optional Parameters The following parameters are supported: -- `actions_cache`: Enable a GitHub Actions cache for the persistent contents of `cvmfs_cache_base`. Requires `cvmfs_cache_base` to be set. With the default shared cache, it caches `shared/cachedb` plus `shared/00`...`shared/ff`; with `cvmfs_shared_cache=no`, it caches `cachedb` plus `00`...`ff` directly under `cvmfs_cache_base`. +- `actions_cache`: Enable a GitHub Actions cache for the persistent contents of `cvmfs_cache_base`. When `cvmfs_cache_base` is not explicitly set, it defaults to `/opt/cvmfs-cache`. With the default shared cache, it caches `shared/cachedb` plus `shared/00`...`shared/ff`; with `cvmfs_shared_cache=no`, it caches `cachedb` plus `00`...`ff` directly under `cvmfs_cache_base`. - `cvmfs_alien_cache`: If set, use an alien cache at the given location. - `cvmfs_alt_root_path`: If set to yes, use alternative root catalog path. Only required for fixed catalogs (tag / hash) under the alternative path. - `cvmfs_authz_helper`: Full path to an authz helper, overwrites the helper hint in the catalog. @@ -28,7 +28,7 @@ The following parameters are supported: - `cvmfs_auto_update`: If set to no, disables the automatic update of file catalogs. - `cvmfs_backoff_init`: Seconds for the maximum initial backoff when retrying to download data. - `cvmfs_backoff_max`: Maximum backoff in seconds when retrying to download data. -- `cvmfs_cache_base`: Location (directory) of the CernVM-FS cache. The `cvmfs` user must be able to traverse all parent directories and write this directory; on GitHub-hosted runners prefer a path under `/tmp` over the GitHub workspace path. +- `cvmfs_cache_base`: Location (directory) of the CernVM-FS cache. The `cvmfs` user must be able to traverse all parent directories and write this directory; on GitHub-hosted runners prefer a path under `/tmp` over the GitHub workspace path. Defaults to `/opt/cvmfs-cache` when `actions_cache` is `true`. - `cvmfs_catalog_watermark`: Try to release pinned catalogs when their number surpasses the given watermark. Defaults to 1/4 CVMFS_NFILES; explicitly set by shrinkwrap. - `cvmfs_check_permissions`: If set to no, disable checking of file ownership and permissions (open all files). - `cvmfs_claim_ownership`: If set to yes, allows CernVM-FS to claim ownership of files and directories. diff --git a/action.yml b/action.yml index 71809a1..cf25bee 100644 --- a/action.yml +++ b/action.yml @@ -49,7 +49,7 @@ inputs: required: false default: '' cvmfs_cache_base: - description: 'Location (directory) of the CernVM-FS cache. The cvmfs user must be able to traverse all parent directories and write this directory; on GitHub-hosted runners prefer a path such as /opt/cvmfs-cache so the action can prepare it for both the cvmfs user and the runner user.' + description: 'Location (directory) of the CernVM-FS cache. The cvmfs user must be able to traverse all parent directories and write this directory; on GitHub-hosted runners prefer a path such as /opt/cvmfs-cache so the action can prepare it for both the cvmfs user and the runner user. When actions_cache is true and this is left empty, it defaults to /opt/cvmfs-cache.' required: false default: '' cvmfs_catalog_watermark: @@ -343,6 +343,14 @@ inputs: runs: using: "composite" steps: + - id: resolve-cache-base + run: | + cache_base="${{ inputs.cvmfs_cache_base }}" + if [ -z "$cache_base" ] && [ "${{ inputs.actions_cache }}" == "true" ]; then + cache_base="/opt/cvmfs-cache" + fi + echo "path=${cache_base}" >> $GITHUB_OUTPUT + shell: bash - id: lsb-release run: | if [ "$RUNNER_OS" == "Linux" ]; then @@ -368,29 +376,29 @@ runs: key: cvmfs-apt-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }} path: | ${{ inputs.apt_cache }} - - if: ${{ inputs.cvmfs_cache_base != '' }} + - if: ${{ steps.resolve-cache-base.outputs.path != '' }} run: | - sudo mkdir -p "${{ inputs.cvmfs_cache_base }}" - sudo chmod a+rwx "${{ inputs.cvmfs_cache_base }}" + sudo mkdir -p "${{ steps.resolve-cache-base.outputs.path }}" + sudo chmod a+rwx "${{ steps.resolve-cache-base.outputs.path }}" shell: bash - uses: actions/cache@v5 - if: ${{ inputs.actions_cache == 'true' && inputs.cvmfs_cache_base != '' && inputs.cvmfs_shared_cache != 'no' }} + if: ${{ inputs.actions_cache == 'true' && steps.resolve-cache-base.outputs.path != '' && inputs.cvmfs_shared_cache != 'no' }} with: key: cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }}-${{ github.run_id }} restore-keys: | cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }}- path: | - ${{ inputs.cvmfs_cache_base }}/shared/cachedb - ${{ inputs.cvmfs_cache_base }}/shared/[0-9a-f][0-9a-f] + ${{ steps.resolve-cache-base.outputs.path }}/shared/cachedb + ${{ steps.resolve-cache-base.outputs.path }}/shared/[0-9a-f][0-9a-f] - uses: actions/cache@v5 - if: ${{ inputs.actions_cache == 'true' && inputs.cvmfs_cache_base != '' && inputs.cvmfs_shared_cache == 'no' }} + if: ${{ inputs.actions_cache == 'true' && steps.resolve-cache-base.outputs.path != '' && inputs.cvmfs_shared_cache == 'no' }} with: key: cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }}-${{ github.run_id }} restore-keys: | cvmfs-cache-${{ steps.lsb-release.outputs.id-release }}-${{ steps.lsb-release.outputs.arch }}-${{ steps.lsb-release.outputs.cvmfs_action_version }}- path: | - ${{ inputs.cvmfs_cache_base }}/cachedb - ${{ inputs.cvmfs_cache_base }}/[0-9a-f][0-9a-f] + ${{ steps.resolve-cache-base.outputs.path }}/cachedb + ${{ steps.resolve-cache-base.outputs.path }}/[0-9a-f][0-9a-f] - run: | ${{ github.action_path }}/setup-cvmfs.sh shell: bash @@ -405,7 +413,7 @@ runs: CVMFS_AUTO_UPDATE: ${{ inputs.cvmfs_auto_update }} CVMFS_BACKOFF_INIT: ${{ inputs.cvmfs_backoff_init }} CVMFS_BACKOFF_MAX: ${{ inputs.cvmfs_backoff_max }} - CVMFS_CACHE_BASE: ${{ inputs.cvmfs_cache_base }} + CVMFS_CACHE_BASE: ${{ steps.resolve-cache-base.outputs.path }} CVMFS_CATALOG_WATERMARK: ${{ inputs.cvmfs_catalog_watermark }} CVMFS_CHECK_PERMISSIONS: ${{ inputs.cvmfs_check_permissions }} CVMFS_CLAIM_OWNERSHIP: ${{ inputs.cvmfs_claim_ownership }} @@ -478,7 +486,7 @@ runs: CVMFS_UBUNTU_DEB_LOCATION: ${{ inputs.cvmfs_ubuntu_deb_location }} CVMFS_MACOS_PKG_LOCATION: ${{ inputs.cvmfs_macos_pkg_location }} CVMFS_CONFIG_PACKAGE: ${{ inputs.cvmfs_config_package }} - - if: ${{ inputs.actions_cache == 'true' && inputs.cvmfs_cache_base != '' && inputs.cvmfs_cache_max_size != '0' }} + - if: ${{ inputs.actions_cache == 'true' && steps.resolve-cache-base.outputs.path != '' && inputs.cvmfs_cache_max_size != '0' }} run: | # Trim the CernVM-FS cache to the configured maximum size if [ -n "${{ inputs.cvmfs_repositories }}" ]; then From 9aa73046ef4275c9f84fd8deb714cbec0048823e Mon Sep 17 00:00:00 2001 From: Valentin Volkl Date: Tue, 31 Mar 2026 17:12:31 +0200 Subject: [PATCH 5/8] try to fix permissions on /opt/cvmfs-cache/shared --- setup-cvmfs.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/setup-cvmfs.sh b/setup-cvmfs.sh index 62fa007..9ff6648 100755 --- a/setup-cvmfs.sh +++ b/setup-cvmfs.sh @@ -51,9 +51,20 @@ if [ "$(uname)" == "Linux" ]; then if [ -n "${CVMFS_CACHE_BASE}" ]; then echo "::group::Preparing cvmfs cache base" mkdir -p "${CVMFS_CACHE_BASE}" + # Ensure the shared sub-directory exists so default ACLs are set on it too + if [ "${CVMFS_SHARED_CACHE}" != "no" ]; then + mkdir -p "${CVMFS_CACHE_BASE}/shared" + fi sudo chown -R cvmfs:root "${CVMFS_CACHE_BASE}" sudo chmod -R a+rwX "${CVMFS_CACHE_BASE}" sudo find "${CVMFS_CACHE_BASE}" -type d -exec chmod a+rwx {} + + # Set default POSIX ACLs so that files/directories created later by the + # cvmfs user (during the job) are world-readable. This is required for + # the actions/cache post-job save step, which runs as the runner user. + if command -v setfacl >/dev/null 2>&1; then + sudo setfacl -R -d -m o::rwX "${CVMFS_CACHE_BASE}" + sudo setfacl -R -m o::rwX "${CVMFS_CACHE_BASE}" + fi id cvmfs sudo -u cvmfs id ls -ld "${CVMFS_CACHE_BASE}" From 8344923afb1048b7683231602b7b4a547e562501 Mon Sep 17 00:00:00 2001 From: Valentin Volkl Date: Wed, 1 Apr 2026 09:54:38 +0200 Subject: [PATCH 6/8] move cvmfs cache base to /Users/vavolkl/.cache --- .github/workflows/cache.yml | 2 +- action.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml index b564222..a2cfa55 100644 --- a/.github/workflows/cache.yml +++ b/.github/workflows/cache.yml @@ -6,7 +6,7 @@ on: - cron: '21 6 * * *' env: - CVMFS_CACHE_BASE: /opt/cvmfs-cache + CVMFS_CACHE_BASE: ~/.cache/cvmfs-cache jobs: populate-cache: diff --git a/action.yml b/action.yml index cf25bee..725c748 100644 --- a/action.yml +++ b/action.yml @@ -49,7 +49,7 @@ inputs: required: false default: '' cvmfs_cache_base: - description: 'Location (directory) of the CernVM-FS cache. The cvmfs user must be able to traverse all parent directories and write this directory; on GitHub-hosted runners prefer a path such as /opt/cvmfs-cache so the action can prepare it for both the cvmfs user and the runner user. When actions_cache is true and this is left empty, it defaults to /opt/cvmfs-cache.' + description: 'Location (directory) of the CernVM-FS cache. The cvmfs user must be able to traverse all parent directories and write this directory. When actions_cache is true and this is left empty, it defaults to $HOME/.cache/cvmfs-cache.' required: false default: '' cvmfs_catalog_watermark: @@ -347,7 +347,7 @@ runs: run: | cache_base="${{ inputs.cvmfs_cache_base }}" if [ -z "$cache_base" ] && [ "${{ inputs.actions_cache }}" == "true" ]; then - cache_base="/opt/cvmfs-cache" + cache_base="${HOME}/.cache/cvmfs-cache" fi echo "path=${cache_base}" >> $GITHUB_OUTPUT shell: bash From 196a02309e03c5281edb980c6f673213e52609e9 Mon Sep 17 00:00:00 2001 From: Valentin Volkl Date: Wed, 1 Apr 2026 10:05:52 +0200 Subject: [PATCH 7/8] change default for cache base yet again --- .github/workflows/cache.yml | 4 ++-- action.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cache.yml b/.github/workflows/cache.yml index a2cfa55..e03de2c 100644 --- a/.github/workflows/cache.yml +++ b/.github/workflows/cache.yml @@ -6,7 +6,7 @@ on: - cron: '21 6 * * *' env: - CVMFS_CACHE_BASE: ~/.cache/cvmfs-cache + CVMFS_CACHE_BASE: ~/.cvmfs-cache jobs: populate-cache: @@ -318,4 +318,4 @@ jobs: if [ -d "${{ env.CVMFS_CACHE_BASE }}" ]; then sudo chmod -R a+rwX "${{ env.CVMFS_CACHE_BASE }}" sudo find "${{ env.CVMFS_CACHE_BASE }}" -type d -exec chmod a+rwx {} + - fi \ No newline at end of file + fi diff --git a/action.yml b/action.yml index 725c748..c8d45c8 100644 --- a/action.yml +++ b/action.yml @@ -49,7 +49,7 @@ inputs: required: false default: '' cvmfs_cache_base: - description: 'Location (directory) of the CernVM-FS cache. The cvmfs user must be able to traverse all parent directories and write this directory. When actions_cache is true and this is left empty, it defaults to $HOME/.cache/cvmfs-cache.' + description: 'Location (directory) of the CernVM-FS cache. The cvmfs user must be able to traverse all parent directories and write this directory. When actions_cache is true and this is left empty, it defaults to $HOME/.cvmfs-cache.' required: false default: '' cvmfs_catalog_watermark: From b1115d7787de5ab6e414b5c0fd1729a7ca5a75f7 Mon Sep 17 00:00:00 2001 From: Valentin Volkl Date: Wed, 1 Apr 2026 10:36:19 +0200 Subject: [PATCH 8/8] change default for cache base yet again --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index c8d45c8..4bc2d85 100644 --- a/action.yml +++ b/action.yml @@ -347,7 +347,7 @@ runs: run: | cache_base="${{ inputs.cvmfs_cache_base }}" if [ -z "$cache_base" ] && [ "${{ inputs.actions_cache }}" == "true" ]; then - cache_base="${HOME}/.cache/cvmfs-cache" + cache_base="${HOME}/.cvmfs-cache" fi echo "path=${cache_base}" >> $GITHUB_OUTPUT shell: bash