Created
April 22, 2026 12:33
-
-
Save hsmalley/16ba4ec4babfd51ce6f24db86493407f to your computer and use it in GitHub Desktop.
Steam Cache Warmup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| steam_root="${HOME}/.local/share/Steam" | |
| games_root="${HOME}/Games" | |
| # Raise or lower this if you want to include larger or smaller cache artifacts. | |
| shader_cache_max_file_size="${SHADER_CACHE_MAX_FILE_SIZE:-5g}" | |
| # These are filename fragments commonly used by Steam, Proton, DXVK, and games | |
| # for shader and pipeline caches. The script turns them into repeated `-I` | |
| # filters so vmtouch only operates on likely hot cache files. | |
| include_patterns=( | |
| 'shader' | |
| 'cache' | |
| 'pipeline' | |
| 'precache' | |
| 'foz' | |
| 'vulkan' | |
| ) | |
| targets=() | |
| include_args=() | |
| tmpdir="" | |
| # Print consistent progress messages so it is easy to see which phase is running. | |
| log() { | |
| printf '==> %s\n' "$*" | |
| } | |
| # Exit early when a required external command is not available. | |
| require_cmd() { | |
| command -v "$1" >/dev/null 2>&1 || { | |
| printf 'error: missing required command: %s\n' "$1" >&2 | |
| exit 1 | |
| } | |
| } | |
| # Remove the temporary directory created for the batch file list. | |
| cleanup() { | |
| [[ -n "${tmpdir}" && -d "${tmpdir}" ]] && rm -rf "${tmpdir}" | |
| } | |
| # Verify the external tools this script depends on before doing any work. | |
| setup_requirements() { | |
| require_cmd vmtouch | |
| require_cmd pkill | |
| require_cmd mktemp | |
| } | |
| # Add a directory to the target list only when it exists on this machine. | |
| add_target_if_exists() { | |
| local path="$1" | |
| [[ -d "$path" ]] || return 0 | |
| targets+=("$path") | |
| } | |
| # Collect the root directories that should be searched for cache-like files. | |
| discover_target_paths() { | |
| add_target_if_exists "$steam_root" | |
| add_target_if_exists "$games_root" | |
| } | |
| # Turn the simple pattern list into repeated `vmtouch -I` arguments. | |
| build_include_args() { | |
| local pattern | |
| include_args=() | |
| for pattern in "${include_patterns[@]}"; do | |
| include_args+=(-I "*${pattern}*") | |
| done | |
| } | |
| # Create a temporary working directory and register cleanup for script exit. | |
| create_tmpdir() { | |
| tmpdir="$(mktemp -d)" | |
| trap cleanup EXIT | |
| } | |
| # Build a NUL-delimited file list that `vmtouch -b -0` can consume safely. | |
| build_file_list() { | |
| local output_file="$1" | |
| # `fd` is usually much faster than `find` for a large recursive file walk, | |
| # so prefer it when present and fall back to portable tools otherwise. | |
| if command -v fd >/dev/null 2>&1; then | |
| fd -0 -t f . "${targets[@]}" >"$output_file" | |
| return | |
| fi | |
| if command -v fdfind >/dev/null 2>&1; then | |
| fdfind -0 -t f . "${targets[@]}" >"$output_file" | |
| return | |
| fi | |
| find "${targets[@]}" -type f -print0 >"$output_file" | |
| } | |
| # Stop older background lock daemons so a new run starts from a clean state. | |
| stop_existing_vmtouch() { | |
| log "Stopping existing vmtouch processes for user ${USER}" | |
| pkill -x -u "$(id -u)" vmtouch >/dev/null 2>&1 || true | |
| } | |
| # Start a background daemon that locks matching cache pages into RAM. | |
| lock_cache_files() { | |
| # `-l` pins matching file pages with mlock(2), and `-d` keeps that lock | |
| # alive in the background after the initial crawl completes. | |
| log "Starting background cache lock" | |
| vmtouch -d -l "${include_args[@]}" "${targets[@]}" | |
| } | |
| # Touch matching cache files once so their pages are faulted into the page cache. | |
| warm_cache_files() { | |
| file_list="${tmpdir}/files.list" | |
| log "Building file list" | |
| build_file_list "$file_list" | |
| # The warm pass reads matching files through a batch list instead of making | |
| # vmtouch rescan the directory tree itself. This keeps the discovery step to | |
| # a single filesystem walk per run. | |
| log "Warming cache-like files up to ${shader_cache_max_file_size}" | |
| vmtouch -t -m "${shader_cache_max_file_size}" "${include_args[@]}" -b "$file_list" -0 2>/dev/null | |
| } | |
| # Orchestrate setup, discovery, locking, and warming in a predictable order. | |
| main() { | |
| setup_requirements | |
| discover_target_paths | |
| if ((${#targets[@]} == 0)); then | |
| log "No cache targets found; nothing to warm." | |
| exit 0 | |
| fi | |
| build_include_args | |
| create_tmpdir | |
| stop_existing_vmtouch | |
| lock_cache_files | |
| warm_cache_files | |
| log "Warm-up complete" | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment