-
Flash Backup script by Hawkins (From: [Plugin] Appdata.Backup)
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
-
Unraid Deck - A beautiful, native mobile client built for the new Unraid API
I am getting error when trying to connect. Where would you like me to report the errors?
-
Additional Scripts For User.Scripts Plugin
I wanted to make this prettier and more informative. Feedback welcome. #!/bin/bash # ============================================================================== # Docker Container Log Cleanup Script # ============================================================================== # This script displays Docker container log sizes before zeroing them # Output shows size in bytes, container name, and total size # Designed for Unraid/Linux systems # ============================================================================== # HTML color code variables for output formatting COLOR_RED="<font color='red'>" COLOR_GREEN="<font color='green'>" COLOR_BLUE="<font color='blue'>" COLOR_END="</font>" COLOR_RED_BOLD="<font color='red'><b>" COLOR_GREEN_BOLD="<font color='green'><b>" COLOR_BLUE_BOLD="<font color='blue'><b>" COLOR_END_BOLD="</b></font>" # Function to display log sizes for all containers display_log_sizes() { docker ps -qa | while read -r container_id; do # Get the log file path for this container log_path=$(docker inspect --format='{{.LogPath}}' "$container_id") # Check if log path exists and is a file if [ -n "$log_path" ] && [ -f "$log_path" ]; then # Get size in bytes size=$(stat -c%s "$log_path" 2>/dev/null) # Get container name name=$(docker ps --format "{{.Names}}" --filter "id=$container_id") # Only include non-zero sizes if [ -n "$size" ] && [ "$size" != "0" ]; then echo "$size|$name" fi fi done | sort -n | awk -F'|' ' { # Store sizes and names in arrays sizes[NR] = $1 names[NR] = $2 total += $1 # Track the maximum width needed len = length($1) if (len > max_width) { max_width = len } } END { # Ensure total width is also considered total_len = length(total) if (total_len > max_width) { max_width = total_len } # Print all entries with dynamic padding for (i = 1; i <= NR; i++) { printf "%0*d bytes = %s\n", max_width, sizes[i], names[i] } # Print separator matching the width of the numbers for (j = 0; j < max_width + 6; j++) { printf "=" } printf "\n" printf "%0*d bytes TOTAL\n", max_width, total }' } # ============================================================================== # Display current log sizes # ============================================================================== echo "Script to display Docker container log sizes in sorted order." echo "Output shows size in bytes, container name, and total size." echo "" echo "${COLOR_BLUE_BOLD}===== Current Log Sizes ===============================================${COLOR_END_BOLD}" echo "" display_log_sizes echo "" # ============================================================================== # Zero out all log files # ============================================================================== echo "${COLOR_GREEN_BOLD}===== Zeroing Logs ====================================================${COLOR_END_BOLD}" echo "" echo "${COLOR_GREEN}Cleaning Logs${COLOR_END}" # Find all Docker log files and zero them out while read -r log; do # Get size in bytes size=$(stat -c%s "$log" 2>/dev/null) # Only include non-zero sizes if [ -n "$size" ] && [ "$size" != "0" ]; then # Extract just the log filename for display filename=$(basename "$log") printf "${COLOR_GREEN}Zeroing out %s${COLOR_END}" "$filename" # Zero out the log file if cat /dev/null > "$log" 2>/dev/null; then echo "${COLOR_GREEN} ... done.${COLOR_END}" else echo "${COLOR_RED_BOLD} ... failed!${COLOR_END_BOLD}" echo "${COLOR_RED_BOLD}Error: Failed to zero out log file. Exiting.${COLOR_END_BOLD}" exit 1 fi fi done < <(find /var/lib/docker/containers/ -name '*.log') echo "${COLOR_GREEN}Cleaning complete!${COLOR_END}" echo ""
-
-
[Plugin] CA Appdata Backup / Restore v2
Watch state: I don't think it does. The watch state is stored in a SQLite database. You can read more about it here. Repulling metadata: Correct, but that is simply a matter of time. Backing up Plex's entire directory takes me a little under an hour and eats many 10's of GB of disk space. That means all containers are down, there is extra wear/tear on my disks, and I am backing up data that is neither unique nor user-created. I can simply have Plex pull all that data down again should I need it. Manually or automatically? If manually: cp <destination_directory_configured_in_plugin>\<name_of_backup_file> <location_to_keep_second_backup> If automatically, create a script with all the cp commands you need and run it automatically using either cron on a script-running plugin. Hear, hear! I already offered, now I am offering again. PayPal? Venmo? Charitable donation to a cause you feel strongly about?
-
[Plugin] CA Appdata Backup / Restore v2
I can help here. Open the unRAID terminal and go to your backup Destination Share: cd <Destination_Share> Find the backup you are looking for: ls Go into the directory of the backup you want to restore from: cd <backup_directory> Find the backup file you are looking for. In my example I am looking for <any>swag<any>: ls *swag* [Used only for demo purposes, don't do this.] I created a temp directory that I'll later delete: mkdir /mnt/cache/appdata/_testing_ Extract the backup file from step 4 into your appdata directory [I used my temp directory.]: tar -zxvf <backup_filename> -C /mnt/cache/appdata/
-
[Plugin] CA Appdata Backup / Restore v2
Just type them in here, each separated by a comma: As for what to type in that field, I use QDirStat running in a Docker container to figure out where the large (in file size and in file count) directories are. I think it would be *stellar* to see that kind of visibility within the backup plugin itself. Something like this: Now you can see why I picked to exclude these directories. I second this! I've been bitten by the 'days old' setting before. If you configure it to backup every n days and you set the 'days old' setting to a number less than n, I believe it will delete your previous backups (all of them!). I think it would be great to have setting like, "keep 3 backs minimum, regardless of age, and 6 backups maximum".
-
[Plugin] CA Appdata Backup / Restore v2
Excellent work. My backups are much, much faster now that plex's and radarr's metadata are excluded: root@Rack:/mnt/user/backups/Community_Applications/Backups/Appdata_Backup# du -h --max-depth=1 && ls -lh 2022-12-05\@09.56/CA_backup_plex.tar.gz && ls -lh 2022-12-05\@11.16/CA_backup_plex.tar.gz 13G ./[email protected] 94G ./[email protected] 105G ./[email protected] 105G ./[email protected] 94G ./[email protected] 409G . -rw-r--r-- 1 root root 77G Dec 5 10:44 2022-12-05\@09.56/CA_backup_plex.tar.gz -rw-r--r-- 1 root root 3.8G Dec 5 11:25 2022-12-05\@11.16/CA_backup_plex.tar.gz root@Rack:/mnt/user/backups/Community_Applications/Backups/Appdata_Backup# The 94G and 105G backups are from me testing the (at the time) broken exclude feature both with (94G) and without (105G) compression. The archive for plex dropped from 77G compressed to just 3.8GB compressed! For anyone interested, I exclude: /mnt/user/appdata/plex/Library/Application Support/Plex Media Server/Cache/, /mnt/user/appdata/plex/Library/Application Support/Plex Media Server/Media/, /mnt/user/appdata/plex/Library/Application Support/Plex Media Server/Metadata/, /mnt/user/appdata/radarr/MediaCover/
-
[Plugin] CA Appdata Backup / Restore v2
Dang it, just confirmed the same on my end. I was going off what I had read earlier in this thread. I am sure @KluthR will figure it out.
-
[Plugin] CA Appdata Backup / Restore v2
Try changing the exclude paths from /mnt/user/appdata/foo/bar/ to /mnt/cache/appdata/foo/bar/. This isn't a permanent fix in my opinion, but I believe it will work until the developer can figure it out. Speaking of that, @KluthR, you're a rockstar. Thanks for the hard work! I am having the same issue and I wanted to see what you thought about it. Also, what is the best way to buy you a few beers?
-
[Support] IBRACORP - All images and files
Done.
-
[Support] IBRACORP - All images and files
No luck with a wipe/reinstall of the container. Also killed the appdata directory, made no difference. Both containers are on the same network type (proxy network for SWAG). Interested in some live troubleshooting this week?
-
[Support] IBRACORP - All images and files
RE: Cachet When starting Cachet I get the following error: Initializing Cachet container ... Attempting to connect to database ... ........................................................... Could not connect to database server! Aborting... I am using MariaDB in Docker, and it is known to be working prior to Cachet. Login below uses the password via copy/paste from the Cachet Docker config screen in unRAID so there are no typos: root@0c2f1410a349:/# mysql -u cachet -p Warning: World-writable config file '/etc/mysql/conf.d/custom.cnf' is ignored Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 70 Server version: 10.4.18-MariaDB-1:10.4.18+maria~bionic-log mariadb.org binary distribution Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> show databases; +--------------------+ | Database | +--------------------+ | cachet | | information_schema | +--------------------+ 2 rows in set (0.001 sec) MariaDB [(none)]> use cachet Database changed MariaDB [cachet]> show tables; Empty set (0.000 sec) MariaDB [cachet]> Here is the relevant data from the Cachet config: Proof that IP and port are correct: Error when starting Cachet: I feel like I am missing something simple here.
-
[Support] SpaceinvaderOne - Macinabox
This might prove useful to those unable to get Big Sur downloaded and running: https://github.com/munki/macadmin-scripts/blob/main/installinstallmacos.py This script can download the Big Sur install image: Product ID Version Build Date Title 001-86606 11.1 20C69 2020-12-14 macOS Big Sur That install file is 12.22 GB when completely downloaded, and you'll need to download a copy and then convert it (the script does this for you) so you'll need about 25GB of free space.
-
Passwordless SSH login
Please be more specific. Which plugin? What are your install steps? What error are you getting?
-
Hawkins changed their profile photo
-
[Support] binhex - Lidarr
Running this container via unRAID. No issues until i upgraded unRAID to latest 6.5 stable release tonight. All docker containers are fine except Lidarr. Starts with : root@localhost:# /usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name='lidarr' --net='bridge' -e TZ="America/Los_Angeles" -e HOST_OS="unRAID" -e 'UMASK'='000' -e 'PUID'='99' -e 'PGID'='100' -p '8004:8686/tcp' -v '/mnt/user/incoming/complete/music/':'/data':'rw' -v '/mnt/user/media/music/':'/media':'rw' -v '/mnt/user/appdata/lidarr':'/config':'rw' 'binhex/arch-lidarr' Log: 2018-03-17 00:09:17.989262 [info] Host is running unRAID 2018-03-17 00:09:18.026926 [info] System information Linux 561862a36433 4.14.26-unRAID #1 SMP PREEMPT Mon Mar 12 16:21:20 PDT 2018 x86_64 GNU/Linux 2018-03-17 00:09:18.071579 [info] PUID defined as '99' 2018-03-17 00:09:18.118826 [info] PGID defined as '100' 2018-03-17 00:09:18.359515 [info] UMASK defined as '000' 2018-03-17 00:09:18.402730 [info] Permissions already set for volume mappings chown: cannot access '/var/lib/lidarr': No such file or directory Blew away the container twice, including the files in /mnt/user/appdata/lidarr:/config. Anyone else getting this?