You know that moment when your server suddenly decides it’s had enough and refuses to write any more data? Yeah, that’s usually when you discover your disk is at 100% and has been crying for help for weeks. Fun times.
Setting up NTFY with Docker: NTFY Self-Hosted Push Notification Service
The Problem: Disk Space Sneaks Up on You
Here’s the thing about disk space – it’s like that pile of laundry in the corner. You know it’s growing, but you keep telling yourself “I’ll deal with it later.” Except with disk space, “later” often means “when everything breaks at 2:00.”
Sure, you could SSH into your server every day and run df -h like some kind of digital janitor. Or you could set up a proper monitoring solution that costs more than your coffee budget. But who has time for that?
The Solution: A Bash Script That Actually Works
So I built this little bash script that does all the heavy lifting for you. And when I say “all,” I mean ALL:
- Basic disk usage – The usual suspects: how much space you have, how much you’re using, and how much panic you should be in / QUICK CHECK
- Inode tracking – Because running out of inodes is like running out of parking spaces even when the lot is half empty / PRETTY QUICK
- I/O statistics – See exactly how much your disk is sweating under pressure / PRETTY QUICK
- Top space hogs – Find out which directories are eating all your storage (looking at you, Docker images) / TAKES TIME
- Smart alerts – Warning levels that actually make sense: OK → Notice → Warning → CRITICAL
The Cool Part: Notifications That Don’t Suck
Here’s where it gets fun. The script can shoot notifications straight to your phone via ntfy. No complicated setup, no enterprise monitoring platform, just a simple curl command and boom – you’re informed.
And it’s smart about it too:
- Disk at 65%? No notification, you’re good.
- Hit 70%? You get a friendly heads-up with a ⚡ emoji.
- Climbing to 80%? Now you get a warning with proper urgency.
- 90%+? DEFCON 1 – urgent priority notification with skull emojis because at this point, we’re past subtle hints.
Pick Your Sections, Skip the Noise
The best part? You don’t need to see everything every time. Just want a quick check on disk usage? Use -s basic. Need to hunt down what’s eating your space? Go with -s basic,analysis,consumers.
It’s like ordering à la carte at a restaurant, except instead of overpriced appetizers, you’re getting exactly the disk stats you need.
Command Line Magic
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Quick daily check ./check_sda1_usage.sh -s basic,analysis # Deep dive when something's wrong ./check_sda1_usage.sh -v -s all # Set it and forget it (cron job that notifies you) ./check_sda1_usage.sh --ntfy # "I need ALL the data RIGHT NOW" ./check_sda1_usage.sh -v --ntfy-always |
The Technical Bits (For us / the Nerds)
Under the hood, it’s collecting everything into a single OUTPUT variable and dumping it all at once at the end. Why? Because watching text scroll line by line is so 1990s, and also because it makes the ntfy notifications actually readable.
It uses df, lsblk, /proc/diskstats, and a bunch of other Unix utilities that have been around since dinosaurs roamed the data centers. Nothing fancy, nothing that’ll break in the next kernel update.
Configuration: Three Lines and You’re Done
At the top of the script:
|
1 2 3 4 5 |
NTFY_URL="https://your-ntfy-server.com/topic" NTFY_TOKEN="your_token_here" NTFY_TITLE="Whatever Title You Want" |
Change those three lines to match your setup, and you’re golden. No YAML files, no JSON configuration, no “config.d” directories with seventeen different files. Just three variables. As it should be.
Real Talk: Why This Actually Helps
Look, we’ve all been there. You’re working on something important, deadline’s looming, and suddenly your deploy fails because there’s no disk space. Then you spend an hour hunting down what filled it up, clearing cache files, and cursing past-you for not setting up monitoring.
This script is basically future-you doing a solid for present-you. Set it up once in a cron job, point it at your ntfy instance, and forget about it until you actually need to know.
The “Oh Crap” Scenarios It Handles
- Log files gone wild? The “consumers” section shows you exactly which directory is the culprit.
- Mystery disk usage? The analysis section tells you if it’s space or inodes causing the problem.
- Performance issues? Check the I/O stats to see if your disk is basically crying.
- Multiple disks? Just change the device parameter:
-d /dev/sdb1
Bonus: It’s All Open Source
The whole thing is a bash script you can read, understand, and modify in about 10 minutes. No black boxes, no “trust us it works,” no vendor lock-in. Just good old-fashioned shell scripting that does exactly what it says on the tin.
Example Output (Basic section)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
✅ CLOUD SERVER: Harddisk Usage ======================================== Disk Statistics Report for /dev/sda1 ======================================== Report generated: 2025-11-25 19:20:01 ========== BASIC DISK USAGE ========== Filesystem: /dev/sda1 Total Size: 150G Used: 84G Available: 61G Usage: 59% Mount Point: / ======================================== Report completed: 2025-11-25 19:20:01 ======================================== |
Final Thoughts
Monitoring disk space shouldn’t be complicated. You shouldn’t need a PhD in systems administration to know when your server is running out of room. And you definitely shouldn’t need to pay a monthly subscription for something that bash can do in 400 lines.
So yeah, this script exists now. Use it, modify it, improve it, break it and fix it again. That’s the fun part about open source solutions – they’re yours to do with as you please.
And hey, if it saves you from one 2:00 “disk full” panic attack, it’s already paid for itself in prevented stress and saved sleep. It already helping me to stay sane!
Happy monitoring!
SETUP
|
1 2 3 4 5 6 7 8 9 10 11 |
# Create / Edit nano monitor_disk_stats.sh # Make executable chmod +x monitor_disk_stats.sh # Add to crontab and set options crontab -e 20 * * * * /monitor_disk_stats.sh -s basic --ntfy |
THE SCRIPT
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 |
#!/bin/bash # Script to check comprehensive disk statistics # Author: Alex@portalZINE.de # Date: 25.11.2025 ##################################### # CONFIGURATION ##################################### DEFAULT_DEVICE="/dev/sda1" # Default device to monitor # Ntfy Configuration NTFY_URL="https://your-ntfy-server.com/topic" # Leave the ":", when using a token NTFY_TOKEN=":your_token_here" # NTFY_TOKEN="user:password" NTFY_TITLE="Whatever Title You Want" ##################################### # Initialize output variable OUTPUT="" # Function to add to output add_output() { OUTPUT="${OUTPUT}${1}\n" } # Function to display usage usage() { cat << EOF Usage: $(basename "$0") [OPTIONS] [DEVICE] Check comprehensive disk statistics for a device. OPTIONS: -h, --help Show this help message -d, --device DEVICE Specify device to check (e.g., /dev/sda1) -q, --quiet Quiet mode - minimal output -v, --verbose Verbose mode - show all available stats -s, --section NAME Show only specific section(s) (comma-separated) -l, --list-sections List all available sections and exit -n, --ntfy Send notification via ntfy (no console output) --ntfy-always Send ntfy notification AND show console output AVAILABLE SECTIONS: all Show all sections (default) basic Basic disk usage (size, used, available) inodes Inode usage statistics filesystem Filesystem information (type, blocks) mount Mount options device Device information (lsblk output) io I/O statistics (reads, writes, sectors) analysis Usage analysis and warnings consumers Top space consumers (largest directories) ARGUMENTS: DEVICE Device to check (default: $DEFAULT_DEVICE) NTFY CONFIGURATION: Configure ntfy settings at the top of the script: - NTFY_URL Ntfy server URL - NTFY_TOKEN Authentication token - NTFY_TITLE Notification title EXAMPLES: $(basename "$0") # Check default device ($DEFAULT_DEVICE) $(basename "$0") /dev/sdb1 # Check /dev/sdb1 $(basename "$0") -d /dev/nvme0n1p1 # Check NVMe device $(basename "$0") --quiet /dev/sda2 # Quiet mode for /dev/sda2 $(basename "$0") -s basic # Show only basic usage $(basename "$0") -s basic,analysis # Show basic usage and analysis $(basename "$0") -s io,consumers /dev/sdb1 # Show I/O stats and top consumers $(basename "$0") --list-sections # List all available sections $(basename "$0") --ntfy # Send notification only (no output) $(basename "$0") --ntfy-always # Send notification AND show output EOF exit 0 } # Function to list all available sections list_sections() { cat << EOF Available Sections: =================== all - Show all sections (default behavior) basic - Basic disk usage information • Filesystem name • Total size • Used space • Available space • Usage percentage • Mount point inodes - Inode usage statistics • Total inodes • Used inodes • Free inodes • Inode usage percentage filesystem - Filesystem information • Filesystem type (ext4, xfs, etc.) • Block size • Total blocks • Free blocks • Available blocks mount - Mount options • Full mount command output • Mount flags (rw, relatime, etc.) device - Device information • Device name • Size • Type • Filesystem type • Mount point • Label • UUID io - I/O statistics • Read operations • Write operations • Sectors read/written • Read/write time • I/O operations in progress • Extended iostat (verbose mode only) analysis - Usage analysis • Disk space status (OK/WARNING/CRITICAL) • Inode status (OK/WARNING/CRITICAL) • Threshold-based alerts consumers - Top space consumers • Largest directories on the partition • Top 10 (normal) or top 20 (verbose) • Human-readable sizes Usage Examples: $(basename "$0") -s basic # Just the essentials $(basename "$0") -s basic,analysis # Quick health check $(basename "$0") -s io # I/O performance only $(basename "$0") -s all # Everything (same as no -s flag) EOF exit 0 } # Parse command line arguments DEVICE="" QUIET_MODE=false VERBOSE_MODE=false SECTIONS="" NTFY_MODE=false NTFY_ALWAYS=false while [[ $# -gt 0 ]]; do case $1 in -h|--help) usage ;; -l|--list-sections) list_sections ;; -d|--device) DEVICE="$2" shift 2 ;; -q|--quiet) QUIET_MODE=true shift ;; -v|--verbose) VERBOSE_MODE=true shift ;; -s|--section) SECTIONS="$2" shift 2 ;; -n|--ntfy) NTFY_MODE=true shift ;; --ntfy-always) NTFY_ALWAYS=true shift ;; -*) echo "Error: Unknown option: $1" echo "Use -h or --help for usage information" exit 1 ;; *) # Assume it's a device path if [ -z "$DEVICE" ]; then DEVICE="$1" else echo "Error: Multiple devices specified" exit 1 fi shift ;; esac done # Use default device if none specified if [ -z "$DEVICE" ]; then DEVICE="$DEFAULT_DEVICE" fi # Function to check if a section should be displayed should_show_section() { local section=$1 # If no sections specified, show all if [ -z "$SECTIONS" ]; then return 0 fi # If 'all' is specified, show everything if [[ ",$SECTIONS," == *",all,"* ]]; then return 0 fi # Check if section is in the comma-separated list if [[ ",$SECTIONS," == *",$section,"* ]]; then return 0 fi return 1 } add_output "========================================" add_output "Disk Statistics Report for $DEVICE" add_output "========================================" if [ "$QUIET_MODE" = false ]; then add_output "Report generated: $(date '+%Y-%m-%d %H:%M:%S')" fi add_output "" # Check if device exists if [ ! -b "$DEVICE" ]; then echo "Error: $DEVICE does not exist or is not a block device" exit 1 fi # Get mount point MOUNT_POINT=$(df "$DEVICE" 2>/dev/null | awk 'NR==2 {print $6}') if [ -z "$MOUNT_POINT" ]; then echo "Error: $DEVICE is not mounted" exit 1 fi # Quiet mode - show only essential info if [ "$QUIET_MODE" = true ]; then QUIET_INFO=$(df -h "$DEVICE" | awk 'NR==2 { print "Device: " $1 print "Size: " $2 print "Used: " $3 print "Available: " $4 print "Usage: " $5 print "Mount: " $6 }') add_output "$QUIET_INFO" USAGE_PERCENT=$(df "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$USAGE_PERCENT" -ge 90 ]; then add_output "Status: ⚠️ CRITICAL (${USAGE_PERCENT}%)" elif [ "$USAGE_PERCENT" -ge 80 ]; then add_output "Status: ⚠️ WARNING (${USAGE_PERCENT}%)" else add_output "Status: ✓ OK (${USAGE_PERCENT}%)" fi # Print all output at once echo -e "$OUTPUT" exit 0 fi if should_show_section "basic"; then add_output "========== BASIC DISK USAGE ==========" BASIC_USAGE=$(df -h "$DEVICE" | awk 'NR==2 { print " Filesystem: " $1 print " Total Size: " $2 print " Used: " $3 print " Available: " $4 print " Usage: " $5 print " Mount Point: " $6 }') add_output "$BASIC_USAGE" add_output "" fi if should_show_section "inodes"; then add_output "========== INODE USAGE ==========" INODE_USAGE=$(df -hi "$DEVICE" | awk 'NR==2 { print " Total Inodes: " $2 print " Used Inodes: " $3 print " Free Inodes: " $4 print " Inode Usage: " $5 }') add_output "$INODE_USAGE" add_output "" fi if should_show_section "filesystem"; then add_output "========== FILESYSTEM INFORMATION ==========" # Filesystem type FS_TYPE=$(df -T "$DEVICE" | awk 'NR==2 {print $2}') add_output " Filesystem Type: $FS_TYPE" # Block size information BLOCK_SIZE=$(stat -f -c %s "$MOUNT_POINT" 2>/dev/null) if [ -n "$BLOCK_SIZE" ]; then add_output " Block Size: $BLOCK_SIZE bytes" fi # Total blocks TOTAL_BLOCKS=$(stat -f -c %b "$MOUNT_POINT" 2>/dev/null) FREE_BLOCKS=$(stat -f -c %f "$MOUNT_POINT" 2>/dev/null) AVAIL_BLOCKS=$(stat -f -c %a "$MOUNT_POINT" 2>/dev/null) if [ -n "$TOTAL_BLOCKS" ]; then add_output " Total Blocks: $TOTAL_BLOCKS" add_output " Free Blocks: $FREE_BLOCKS" add_output " Available: $AVAIL_BLOCKS" fi add_output "" fi if should_show_section "mount"; then add_output "========== MOUNT OPTIONS ==========" MOUNT_INFO=$(mount | grep "$DEVICE") if [ -n "$MOUNT_INFO" ]; then add_output " $MOUNT_INFO" fi add_output "" fi if should_show_section "device"; then add_output "========== DISK DEVICE INFO ==========" if command -v lsblk &> /dev/null; then LSBLK_INFO=$(lsblk "$DEVICE" -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL,UUID 2>/dev/null | sed 's/^/ /') add_output "$LSBLK_INFO" fi add_output "" fi if should_show_section "io"; then add_output "========== I/O STATISTICS ==========" if [ -f /proc/diskstats ]; then # Extract device name without /dev/ prefix DEVICE_NAME=$(basename "$DEVICE") # Extract stats for the device DISK_STATS=$(grep " $DEVICE_NAME " /proc/diskstats) if [ -n "$DISK_STATS" ]; then IO_STATS=$(echo "$DISK_STATS" | awk '{ print " Device: " $3 print " Reads: " $4 print " Reads Merged: " $5 print " Sectors Read: " $6 print " Read Time (ms): " $7 print " Writes: " $8 print " Writes Merged: " $9 print " Sectors Written: " $10 print " Write Time (ms): " $11 print " I/Os in Progress:" $12 print " I/O Time (ms): " $13 }') add_output "$IO_STATS" fi fi # iostat if available - show in verbose mode or if explicitly installed if command -v iostat &> /dev/null && [ "$VERBOSE_MODE" = true ]; then add_output "" add_output " Extended I/O Stats:" IOSTAT_INFO=$(iostat -x "$DEVICE" 1 1 2>/dev/null | tail -n +4 | sed 's/^/ /') add_output "$IOSTAT_INFO" fi add_output "" fi if should_show_section "analysis"; then add_output "========== USAGE ANALYSIS ==========" # Get usage percentage as a number USAGE_PERCENT=$(df "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//') INODE_PERCENT=$(df -i "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//') # Disk space warning add_output " Disk Space Status:" if [ "$USAGE_PERCENT" -ge 90 ]; then add_output " ⚠️ CRITICAL: Disk usage is critically high (${USAGE_PERCENT}%)!" elif [ "$USAGE_PERCENT" -ge 80 ]; then add_output " ⚠️ WARNING: Disk usage is high (${USAGE_PERCENT}%)" elif [ "$USAGE_PERCENT" -ge 70 ]; then add_output " ⚡ NOTICE: Disk usage is moderate (${USAGE_PERCENT}%)" else add_output " ✓ OK: Disk usage is within normal range (${USAGE_PERCENT}%)" fi # Inode warning add_output "" add_output " Inode Status:" if [ "$INODE_PERCENT" -ge 90 ]; then add_output " ⚠️ CRITICAL: Inode usage is critically high (${INODE_PERCENT}%)!" elif [ "$INODE_PERCENT" -ge 80 ]; then add_output " ⚠️ WARNING: Inode usage is high (${INODE_PERCENT}%)" else add_output " ✓ OK: Inode usage is within normal range (${INODE_PERCENT}%)" fi add_output "" fi if should_show_section "consumers"; then add_output "========== TOP SPACE CONSUMERS ==========" if [ -d "$MOUNT_POINT" ]; then add_output " Largest directories in $MOUNT_POINT:" if [ "$VERBOSE_MODE" = true ]; then # Show top 20 in verbose mode TOP_DIRS=$(du -h "$MOUNT_POINT" --max-depth=1 2>/dev/null | sort -rh | head -20 | sed 's/^/ /') else # Show top 10 in normal mode TOP_DIRS=$(du -h "$MOUNT_POINT" --max-depth=1 2>/dev/null | sort -rh | head -10 | sed 's/^/ /') fi add_output "$TOP_DIRS" fi add_output "" fi add_output "========================================" add_output "Report completed: $(date '+%Y-%m-%d %H:%M:%S')" add_output "========================================" # Function to send ntfy notification send_ntfy() { local message="$1" local priority="${2:-default}" local tags="${3:-}" if [ -z "$NTFY_URL" ] || [ -z "$NTFY_TOKEN" ]; then echo "Error: Ntfy configuration is incomplete" return 1 fi local curl_args=(-u "$NTFY_TOKEN") if [ -n "$NTFY_TITLE" ]; then curl_args+=(-H "Title: $NTFY_TITLE") fi if [ -n "$priority" ] && [ "$priority" != "default" ]; then curl_args+=(-H "Priority: $priority") fi if [ -n "$tags" ]; then curl_args+=(-H "Tags: $tags") fi curl_args+=(-d "$message" "$NTFY_URL") curl -s "${curl_args[@]}" > /dev/null return $? } # Prepare notification if ntfy is enabled if [ "$NTFY_MODE" = true ] || [ "$NTFY_ALWAYS" = true ]; then # Get key metrics for determining priority USAGE_PERCENT=$(df "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//') INODE_PERCENT=$(df -i "$DEVICE" | awk 'NR==2 {print $5}' | sed 's/%//') # Determine priority and tags based on usage PRIORITY="default" TAGS="white_check_mark" if [ "$USAGE_PERCENT" -ge 90 ] || [ "$INODE_PERCENT" -ge 90 ]; then PRIORITY="urgent" TAGS="warning,skull" elif [ "$USAGE_PERCENT" -ge 80 ] || [ "$INODE_PERCENT" -ge 80 ]; then PRIORITY="high" TAGS="warning" elif [ "$USAGE_PERCENT" -ge 70 ] || [ "$INODE_PERCENT" -ge 70 ]; then PRIORITY="default" TAGS="zap" fi # Send the full output as notification NTFY_MESSAGE=$(echo -e "$OUTPUT") # Send notification if send_ntfy "$NTFY_MESSAGE" "$PRIORITY" "$TAGS"; then if [ "$NTFY_MODE" = true ]; then echo "Notification sent successfully to ntfy" exit 0 fi else echo "Error: Failed to send ntfy notification" if [ "$NTFY_MODE" = true ]; then exit 1 fi fi fi # Print all output at once at the end (unless ntfy-only mode) if [ "$NTFY_MODE" = false ]; then echo -e "$OUTPUT" fi |
P.S. – If you’re reading this at 2:00 because your disk is full right now, my condolences. But hey, at least now you have a script for next time.
