Skip to content

Instantly share code, notes, and snippets.

@hsmalley
Created April 22, 2026 12:33
Show Gist options
  • Select an option

  • Save hsmalley/16ba4ec4babfd51ce6f24db86493407f to your computer and use it in GitHub Desktop.

Select an option

Save hsmalley/16ba4ec4babfd51ce6f24db86493407f to your computer and use it in GitHub Desktop.
Steam Cache Warmup
#!/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