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.

Flash Backup script by Hawkins (From: [Plugin] Appdata.Backup)

Featured Replies

After encountering persistent, silent exits when the CA Appdata Backup plugin made it to the Flash backup part of the process, I put together a custom backup script with Claude Code to ensure my boot drive is reliably backed up.

Script Highlights

  • Standardized Format: Generates zips matching official naming conventions (<host>-v<version>-boot-backup-YYYYMMDD-HHMM.zip) with root relative paths.

  • Integrity Checks: Runs unzip -t and explicitly checks for critical files (ident.cfg, super.dat) before keeping the archive.

  • Rollback Handling: Includes a configurable toggle (EXCLUDE_PREVIOUS) to keep or skip /boot/previous files—useful if you run beta releases (like me, v7.4.0-beta.2) and want rollback data included.

  • Failsafes & Maintenance: Features process locking, destination free-space validation, automated pruning of old archives, and Unraid GUI notifications.

  • Flexible Execution: Can be run manually, via cron, or plugged into the CA Appdata Backup post-run hook (Settings -> Appdata Backup/Restore -> Post-run script).

AI Code Disclaimer

This script was authored using Claude Code. While it is actively working in my personal setup, it has not been deeply or widely tested across different hardware configurations or Unraid versions. Please review the script, test it manually in your environment, and verify the resulting archives before relying on it for disaster recovery.

Configuration Requirements

Before running, open the script and update the following variables:

  • DEST: Point this to your array or preferred storage location.

  • EXCLUDE_PREVIOUS: Set to 0 to back up rollback system files, or 1 to exclude them and conserve space.

#!/bin/bash
#
# flash-backup.sh -- local backup of the Unraid boot device (/boot, "flash") to the array.
#
# Intended to be called from CA Appdata Backup:
#   Settings -> Appdata Backup/Restore -> Post-run script
# but it is standalone and can be run by hand or from cron.
#
# Produces, matching the historical naming convention:
#   <host>-v<unraid-version>-boot-backup-YYYYMMDD-HHMM.zip
# e.g.  nas-v7.4.0-beta.2-boot-backup-20260916-1305.zip
#
# Archive paths are relative to /boot (top-level entries are config/, bzimage, ...),
# which matches the archives produced by the Unraid Connect flash backup.
#
# Exit codes: 0 = success, 1 = failure (the plugin surfaces a non-zero exit).
set -uo pipefail
# ---------------------------------------------------------------- configuration
DEST="/mnt/user/backups/processes/unraid_dr/nas/flash"
SRC="/boot"
KEEP=4                      # how many archives to retain (oldest pruned beyond this)
# /boot/previous is the rollback copy of the PREVIOUS Unraid release.
# Currently INCLUDED (EXCLUDE_PREVIOUS=0) because this server runs beta releases
# and /boot/previous is the rollback path. Set to 1 to exclude it and halve the
# archive size once you're back on a stable release.
EXCLUDE_PREVIOUS=0
MIN_FREE_MB=4096            # refuse to start if the destination has less than this
LOCK="/tmp/flash-backup.lock"
NOTIFY="/usr/local/emhttp/webGui/scripts/notify"
# ------------------------------------------------------------- end configuration
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
# Unraid's notify passes its arguments on the command line; an over-long -d blows
# past ARG_MAX and the notification is silently lost. Keep the description short.
notify() {
    local importance="$1" subject="$2" desc="$3"
    [ -x "$NOTIFY" ] || return 0
    desc="${desc:0:200}"
    "$NOTIFY" -e "Flash backup" -s "$subject" -d "$desc" -i "$importance" >/dev/null 2>&1
}
fail() {
    log "ERROR: $*"
    notify "alert" "Flash backup FAILED" "$*"
    exit 1
}
# Only one copy at a time; the plugin and a manual run can otherwise collide.
exec 9>"$LOCK" || fail "cannot create lock file $LOCK"
flock -n 9 || { log "another flash backup is already running -- exiting"; exit 0; }
# ---------------------------------------------------------------- sanity checks
mountpoint -q "$SRC" || fail "$SRC is not mounted"
[ -r "$SRC/config/ident.cfg" ] || fail "$SRC does not look like an Unraid boot device"
command -v zip   >/dev/null || fail "zip not found"
command -v unzip >/dev/null || fail "unzip not found"
mkdir -p "$DEST" || fail "cannot create destination $DEST"
[ -w "$DEST" ] || fail "destination $DEST is not writable"
free_mb=$(df -Pm "$DEST" | awk 'NR==2 {print $4}')
[ "${free_mb:-0}" -ge "$MIN_FREE_MB" ] \
    || fail "only ${free_mb}MB free at $DEST (need ${MIN_FREE_MB}MB)"
# ------------------------------------------------------- derive the archive name
host=$(hostname -s)
[ -n "$host" ] || fail "could not determine hostname"
version=$(sed -n 's/^version="\(.*\)"$/\1/p' /etc/unraid-version)
[ -n "$version" ] || fail "could not read version from /etc/unraid-version"
# Strip anything that has no business in a filename.
host="${host//[^A-Za-z0-9._-]/_}"
version="${version//[^A-Za-z0-9._-]/_}"
stamp=$(date +%Y%m%d-%H%M)
name="${host}-v${version}-boot-backup-${stamp}.zip"
final="$DEST/$name"
tmp="$DEST/.${name}.partial"
[ -e "$final" ] && fail "archive already exists: $final"
# ------------------------------------------------------------------- build it
excludes=()
(( EXCLUDE_PREVIOUS )) && excludes+=( -x 'previous/*' )
log "source      : $SRC"
log "destination : $final"
log "excluding   : $( (( EXCLUDE_PREVIOUS )) && echo 'previous/' || echo '(nothing)' )"
rm -f "$tmp"
start=$SECONDS
# cd into /boot so stored paths are relative (config/, bzimage, ...).
# -r recurse, -q quiet, -X drop extra file attributes, -y store symlinks as links.
( cd "$SRC" && zip -r -q -X -y "$tmp" . "${excludes[@]}" )
rc=$?
# zip: 0 = ok, 12 = "nothing to do". Anything else is a real failure.
if [ "$rc" -ne 0 ] && [ "$rc" -ne 12 ]; then
    rm -f "$tmp"
    fail "zip failed with exit code $rc"
fi
[ -s "$tmp" ] || { rm -f "$tmp"; fail "archive is empty"; }
# ------------------------------------------------------------------- verify it
log "verifying archive integrity..."
if ! unzip -t -qq "$tmp" >/dev/null 2>&1; then
    rm -f "$tmp"
    fail "integrity check failed -- archive discarded"
fi
# The whole point is recovering the array config; refuse to keep an archive
# that somehow lacks it.
for must in "config/ident.cfg" "config/super.dat"; do
    unzip -l "$tmp" "$must" 2>/dev/null | grep -q "$must" \
        || { rm -f "$tmp"; fail "archive is missing $must -- discarded"; }
done
mv -f "$tmp" "$final" || fail "could not move archive into place"
elapsed=$(( SECONDS - start ))
size=$(du -h "$final" | cut -f1)
count=$(unzip -l "$final" 2>/dev/null | tail -1 | awk '{print $2}')
log "created $name ($size, ${count:-?} entries) in ${elapsed}s"
# --------------------------------------------------------------------- prune
mapfile -t old < <(ls -1t "$DEST"/*-boot-backup-*.zip 2>/dev/null | tail -n +$(( KEEP + 1 )))
if [ "${#old[@]}" -gt 0 ]; then
    for f in "${old[@]}"; do
        log "pruning old archive: $(basename "$f")"
        rm -f "$f"
    done
fi
# Clean up any partials left behind by an interrupted earlier run.
find "$DEST" -maxdepth 1 -name '.*.partial' -mtime +1 -delete 2>/dev/null
kept=$(ls -1 "$DEST"/*-boot-backup-*.zip 2>/dev/null | wc -l)
log "done -- $kept archive(s) retained in $DEST"
notify "normal" "Flash backup OK" "$name ($size) in ${elapsed}s; $kept retained"
exit 0

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.