May 12, 20251 yr I have been setting up my first Unraid Server over the last few weeks and keep filling up my cache drive. Found a lot of helpful advice out there. I setup Mover Tuning, got hardlinks working, and returned my 512GB SSD and replaced it with 2TB but that still is not stopping my cache from filling up when I do stupid stuff like download a multi-season show in remux or a collection 4K movies at once. And I don't want to keep waking up and and have to deal with my server being down. The Mover Tuning plugin is great and its "Move All from Primary->Secondary" if above x% is really a key functionality when the cache is filling up. And for traditional cache usage situations, I take advantage of the "age" logic and have new content stay on the cache for a week to seed and get the initial peak of IO from usage and 'arr processing out of the way before transferring to the array for longer term storage. For those 99% / normal situations having the mover run every week is all that I need. If content sticks around on the cache for two weeks instead of one it is no problem as long as there is space on the cache. But weekly won't handle a full cache. I tried moving up the schedule for the mover to run daily and that still wasn't enough to stop a crash, probably because my download client was keeping files locked up and the mover was not able to complete. I didn't want to go to hourly schedule because I would prefer to not have the mover running during the day in "normal" circumstances. So, it was clear that I would need two triggers for the mover, the normal schedule and an "emergency" / "cache full" trigger. I found some different threads out there where people decided to do similar things, but not a complete solution in one place. So I cobbled one together. I am new to script writing so I am sure people can recommend improvements. But this seems to serve my needs well, and I figure others may benefit from it too. Optional Plugins I use in conjunction with this script: "Mover Tuning" and "User Scripts". I keep this script in "User Scripts". What this script does: Should be run frequently, I currently run every 15 minutes (custom cron: */15 * * * *). 99.99% of the time it simply confirms the cache is not full and exits without doing anything. If cache is over the defined threshold it: Confirms this script is not already running Sends a warning notification via Unraid Stops (non-blacklisted) Docker containers Runs the Mover Restarts Docker containers Probably a little more verbose than needed in the script but hopefully it helps people setup / customize to their needs. Credit to Plugin creators and people in threads like this one that did all the real work. I just pasted it together. #!/bin/bash # This script is intended to be run frequently to check if the cache drive is full. # If the cache drive is below the threshold it exits without taking any other action. # If the cache drive is full it stops all Docker Continers and runs the mover. # Set the percent threshold for how full the cache drive is to take action. # IMPORTANT: Make sure Mover will move files if run. Suggest setting "Move All from Primary->Secondary" in Mover Tuning 5% lower than this. pct_threshold="85" lock_file="/tmp/mover_lock" cache_path="/mnt/cache" # Blacklist Docker Containers to not stop / start here Blacklist=( Plex-Media-Server steam-headless ) # List any Docker Containers to be restarted first here StartFirst=( GluetunVPN ) #### Check if cache is below threshold, if true takes no action and exits #### if [[ $(df -h "$cache_path" | awk 'NR==2 {sub(/%/, "", $5); print $5}') -lt "$pct_threshold" ]]; then echo "Cache drive at: " $(df -h "$cache_path" | awk 'NR==2 {sub(/%/, "", $5);print $5}') "% full. Below $pct_threshold% threshold. Taking no action." exit 0 fi echo "Cache drive at: " $(df -h "$cache_path" | awk 'NR==2 {sub(/%/, "", $5);print $5}') "% full! Above $pct_threshold% threshold!" #### Check if the mover is already running #### if [ -f "$lock_file" ]; then echo "Mover already running!" exit 1 fi #### Send Warning #### /usr/local/emhttp/plugins/dynamix/scripts/notify -i warning -s Docker -d "Cache drive is full! Stopping containers and running mover!" #### Touch lock file / prevent script from starting the mover again #### touch "$lock_file" #### Get all Docker Containers #### Containers=$(docker ps -a --format "{{.Names}}") #### Loop thru all containers #### for val in $Containers; do Skip=false #### Skip those in blacklist #### for check in ${Blacklist[@]}; do if [ $val == $check ] then Skip=true fi done #### Stop containers #### if [ $(docker container inspect -f '{{.State.Running}}' $val) == "true" ] && [ $Skip == "false" ] then docker container stop -t 30 $val echo "stopped " $val fi done echo "Stopped all containers (not in blacklist). Running Mover." #### Run Mover #### #### Command to start Mover Tuning: #### /usr/local/emhttp/plugins/ca.mover.tuning/age_mover start #### Command to start OG Mover: #### #mover echo "Mover Done. Starting containers." #### Start the "Start First" containers #### for val in $StartFirst; do Skip=false #### Skip those in blacklist #### for check in ${Blacklist[@]}; do if [ $val == $check ] then Skip=true fi done #### Start containers #### if [ $(docker container inspect -f '{{.State.Running}}' $val) == "false" ] && [ $Skip == "false" ] then docker container start $val echo "started " $val fi done #### Loop thru all containers #### for val in $Containers; do Skip=false #### Skip those in blacklist #### for check in ${Blacklist[@]}; do if [ $val == $check ] then Skip=true fi done #### Start containers #### if [ $(docker container inspect -f '{{.State.Running}}' $val) == "false" ] && [ $Skip == "false" ] then docker container start $val echo "started " $val fi done echo "Started all containers (not in blacklist). Releasing lock file." #### Remove mover lock file #### rm "$lock_file" echo "cacheFull script end" exit 0 Edited May 12, 20251 yr by JTVUS
October 1, 2025Oct 1 Before reading this understand this is not meant as a poke in the eye, just some criticism from somebody or I should say something else. Lol Ran it thru some Ai and it said the following: This is a well-structured bash script for managing cache drive capacity on what appears to be an Unraid server. Here's my analysis:StrengthsClear logic flow: Checks threshold → stops containers → runs mover → restarts containersSafety mechanisms: Lock file prevents concurrent mover runsFlexibility: Configurable blacklist and start-priority containersGood logging: Echo statements track script progressGraceful container shutdown: 30-second timeout for proper stoppingPotential Issues & Suggestions1. Lock file cleanup on failureIf the script crashes after creating the lock file but before removing it, the mover will be permanently blocked. Consider adding:bash# Add trap to ensure cleanup trap 'rm -f "$lock_file"' EXIT ERR INT TERM2. Array iteration vulnerabilityThe Containers variable isn't quoted, which will break on container names with spaces:bash# Current (vulnerable): for val in $Containers; do # Better: while IFS= read -r val; do # ... container logic ... done < <(docker ps -a --format "{{.Names}}")3. Blacklist/StartFirst comparisonUsing == without quotes can cause issues:bash# Current: if [ $val == $check ] # Better: if [[ "$val" == "$check" ]]; then4. Race conditionBetween checking if the lock file exists and creating it, another instance could start. Use atomic operation:bash# Instead of separate check and touch: if ! mkdir "$lock_file" 2>/dev/null; then echo "Mover already running!" exit 1 fi # Then at cleanup: rmdir "$lock_file"5. Error handlingThe mover command has no error checking. Consider:bashif ! /usr/local/emhttp/plugins/ca.mover.tuning/age_mover start; then echo "ERROR: Mover failed!" # Send notification /usr/local/emhttp/plugins/dynamix/scripts/notify -i alert -s Docker -d "Mover failed!" rm "$lock_file" exit 1 fi6. Container state verificationAfter starting containers, verify they're actually running:bash# After the restart loops: sleep 5 # Give containers time to start failed_containers=() for val in $Containers; do if [[ $(docker container inspect -f '{{.State.Running}}' "$val") == "false" ]]; then failed_containers+=("$val") fi done if [[ ${#failed_containers[@]} -gt 0 ]]; then echo "WARNING: Failed to start: ${failed_containers[*]}" fiMinor ImprovementsAdd set -euo pipefail at the top for safer error handlingUse declare -a Blacklist=(...) for explicit array declarationConsider logging to a file for troubleshooting: exec > >(tee -a /var/log/cache_mover.log) 2>&1Overall, this is a solid script with good structure. The main concerns are around edge cases with concurrent execution and error handling.
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.