Skip to content
View in the app

A better way to browse. Learn more.

Unraid

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Cache Full Script

Featured Replies

 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 by JTVUS

  • 4 months later...

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:

Strengths

  • Clear logic flow: Checks threshold → stops containers → runs mover → restarts containers

  • Safety mechanisms: Lock file prevents concurrent mover runs

  • Flexibility: Configurable blacklist and start-priority containers

  • Good logging: Echo statements track script progress

  • Graceful container shutdown: 30-second timeout for proper stopping

Potential Issues & Suggestions

1. Lock file cleanup on failure

If 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 TERM

2. Array iteration vulnerability

The 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 comparison

Using == without quotes can cause issues:

bash

# Current:
if [ $val == $check ]

# Better:
if [[ "$val" == "$check" ]]; then

4. Race condition

Between 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 handling

The mover command has no error checking. Consider:

bash

if ! /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
fi

6. Container state verification

After 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[*]}"
fi

Minor Improvements

  • Add set -euo pipefail at the top for safer error handling

  • Use declare -a Blacklist=(...) for explicit array declaration

  • Consider logging to a file for troubleshooting: exec > >(tee -a /var/log/cache_mover.log) 2>&1

Overall, 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.

Guest
Reply to this topic...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.