├── patch.ins ├── EnterRouterMode.sh ├── README.md └── cpsync.sh /patch.ins: -------------------------------------------------------------------------------- 1 | #CPSYNCMOD 2 | if [ -d /data/UsbDisk2/Volume1/sd_autobackup/ ] && [ -d /data/UsbDisk1/Volume1/DCIM/ ]; then 3 | /etc/udev/script/cpsync.sh /data/UsbDisk1/Volume1/ /data/UsbDisk2/Volume1/sd_autobackup/ & 4 | fi 5 | #ENDMOD 6 | 7 | -------------------------------------------------------------------------------- /EnterRouterMode.sh: -------------------------------------------------------------------------------- 1 | if [ -e /tmp/autoinstalllock ]; then 2 | exit 3 | fi 4 | 5 | touch /tmp/autoinstalllock 6 | 7 | mod=0 8 | scriptdir=${0%/*} 9 | 10 | # to enable root telnet access to ravpower filehub rp-wd03 11 | # uncomment the section below 12 | # if ! [ -e /etc/telnetflag ]; then 13 | # /usr/sbin/telnetd & 14 | # touch /etc/telnetflag 15 | # mod=1 16 | # else 17 | # echo Telnet already active 18 | # fi 19 | 20 | #script klaarzetten 21 | if ! [ -e /etc/udev/script/cpsync.sh ]; then 22 | cp $scriptdir/cpsync.sh /etc/udev/script/ 23 | mod=1 24 | else 25 | echo cpsync.sh already available 26 | fi 27 | #extra opdracht toevoegen aan bestaande usb startscript: 28 | 29 | if ! grep -q '#CPSYNCMOD' /etc/udev/script/add_usb_storage.sh; then 30 | beforeline='echo \"$1: Exit: Normal\" >> \/tmp\/usb_add_info' 31 | position=$(sed -n "/$beforeline/=" /etc/udev/script/add_usb_storage.sh | tail -n1) 32 | position=$((position-1)) 33 | sed -i "$position r $scriptdir/patch.ins" /etc/udev/script/add_usb_storage.sh 34 | mod=1 35 | else 36 | echo USB mounting already includes cpsync call 37 | fi 38 | 39 | if [ $mod -ne '0' ]; then 40 | #if anything changed it needs to be written to nvram 41 | echo Writing etc to flash 42 | /usr/sbin/etc_tools p 43 | else 44 | echo Nothing changed 45 | fi 46 | 47 | sync 48 | rm -f /tmp/autoinstalllock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RP-WD03-SD-backup-tool 2 | 3 | Backup SD-card from your camera to USB on the ravpower filehub RP-WD03 4 | 5 | The Ravpower Filehub wd-03 already has a backup function, but it does not really meet my requirements: 6 | 7 | - Start backup without opening the app 8 | - Synchronize only new images from my camera sd-card 9 | - Move images that have been deleted from the sd-card to a separate folder (trashcan) on the usb stick 10 | 11 | I recently bought the RP-WD009, which has a dedicated back-up button. The ravpower backup tools unfortunately still copies the entire sd-card every time you backup. My modification also works on my RP-WD009 and I would love to trigger it with the backup-key but have not yet found a way to accomplish that. It will work the same as on the RP-WD03. 12 | Development of this backup-tool started on the verbatim mediashare wireless. It will probably also work on that, but i don't have it anymore to test. 13 | 14 | Both the RP-WD03 and the RP-WD009 are excelent devices. Great build quality and support. 15 | 16 | **what will this tool do?** 17 | 18 | Well…uhm… the above ;-) 19 | 20 | I have been using this method many times over the last few years without any issues and am happy to share it … AS IS! I will accept no responsibility or liability for it. Try it, test how it works and decide if you want te keep it. 21 | 22 | **Install** 23 | 24 | Just put the files on a sdcard or usb stick and insert into the filehub. Wait until its done blinking. 25 | Your filehub is now prepared to automatically (intelligently) backup sd cards to usb sticks. You no longer need the installation files. The installation is saved to nvram of the filehub and will survive a reboot. 26 | 27 | **Usage** 28 | 29 | Prepare a usb stick by creating a directory `sd_autobackup` on it 30 | 31 | Insert both the sd-card and the usb stick into the filehub 32 | The filehub will start syncing the files, flashing some leds while it is busy 33 | 34 | A cardID.txt file containing a random uuid will be created on the sd-card, so the backup tool will recognize the same card on future syncs. That means your sd-card can not be read-only. 35 | 36 | When it’s done you should power doen the filehub before removing the card and stick. Otherwise they will not be properly dismounted and you risk data loss. 37 | 38 | **Uninstalling** 39 | 40 | Simply reset the filehub to factory settings using the tiny reset button or the app. The modifications, that are stored in nvram, will be gone. 41 | 42 | **extra** 43 | 44 | Have a look at the `EnterRouterMode` if you uncomment the section that enables telnet then the installation procedure will also enable telnet. Use with caution: it will use the default username (root) and possword (20080826) set by the manufacturer. 45 | 46 | **thanks** 47 | 48 | A few years ago i used a lot of info found on https://github.com/digidem/filehub-config, so many thanks to Digital Democracy. Both tools have developed since than, so it can't hurt to also see what that repo has to offer for you. Their backup routine depends on rsync being avalable on the usb stick. While i would also prefer using rsync, my tool only requires you to create a directory on the usb stick, which you can do on the road using the filehub app. 49 | -------------------------------------------------------------------------------- /cpsync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ######################################################################## 4 | # 5 | # !!This is not a bullit-proof solution! No guaranties!! 6 | # Having said that; it works pretty well for me... 7 | # Using rsync would be saver, but even if you can get rsync ported to 8 | # your platform it will need a lot of resources, that may not be 9 | # available. 10 | # 11 | # This is a simple directory synchronization utility for platforms with very little RAM 12 | # and limited operating systems, like travel routers. 13 | # It can be used in the field to backup sd cards to a usb stick, 14 | # without the need to connect a laptop/tablet/phone etc. 15 | # Every sd card will be backed op in a separate directory, and only 16 | # new files are copied the next time this sync is run on the same card! :-D 17 | # deleted files will be placed in a @Trashcan directory. 18 | # Minimal RAM usage by using files to save file-lists and compare source and target. 19 | # A little more wear on the target drive is negligible compared to the actual 20 | # data copied. 21 | # 22 | # Install to ravpower RD-WD03: 23 | # Edit the existing /etc/udev/script/add_usb_storage.sh script 24 | # to start this tool with SDcard as source and USB as target. 25 | # e.g. add /etc/udev/script/cpsync.sh /data/UsbDisk1/Volume1 /data/UsbDisk2/Volume1 26 | # to the end of the original script. Commit modified /etc to flash after succesful testrun. 27 | # 28 | # This script will very likely need modification to run on other platforms! 29 | # Especially the led notification is dependent on hardware and firmware. 30 | # 31 | # EScape 32 | # Version 2018 33 | # 34 | ######################################################################### 35 | 36 | #Functions for led control on the ravpower RP-WD03. Modify these functions for specific hardware and firmware. 37 | leds_reset () { 38 | # blink wifiled while busy. Back to normal after succes. Statusled keeps blinking after failure. 39 | /usr/sbin/pioctl internet 3 40 | /usr/sbin/pioctl status 3 41 | /usr/sbin/pioctl wifi 3 42 | /usr/sbin/pioctl internet 1 43 | /usr/sbin/pioctl status 1 44 | /usr/sbin/pioctl wifi 1 45 | } 46 | 47 | leds_active () { 48 | # blink wifiled while busy. Back to normal after succes. Statusled keeps blinking after failure. 49 | leds_reset 50 | /usr/sbin/pioctl internet 2 51 | /usr/sbin/pioctl status 2 52 | } 53 | 54 | leds_done () { 55 | # blink wifiled while busy. Back to normal after succes. Statusled keeps blinking after failure. 56 | leds_reset 57 | /usr/sbin/pioctl internet 0 58 | } 59 | 60 | leds_error () { 61 | # blink wifiled while busy. Back to normal after succes. Statusled keeps blinking after failure. 62 | leds_reset 63 | /usr/sbin/pioctl status 2 64 | } 65 | 66 | #Cleanup when script exits 67 | cleanexit () { 68 | filepid=$(cat "/tmp/cpsync.pid") 69 | if [ $filepid -eq $$ ]; then 70 | rm /tmp/cpsync.pid 71 | [ -f /var/lock/wifidg ] && rm /var/lock/wifidg 72 | sync 73 | fi 74 | exit 75 | } 76 | 77 | 78 | #Exit if no valid source and target is found 79 | if [ "$1" = "" ] || [ "$2" = "" ]; then 80 | echo Usage: cpsync sourcedir targetdir 81 | exit 82 | fi 83 | 84 | sourcepath="$1" 85 | targetpath="$2" 86 | 87 | if [ ! -d "$sourcepath" ]; then 88 | echo $sourcepath "(source) does not exist" 89 | exit 90 | fi 91 | if [ ! -d "$targetpath" ]; then 92 | echo $targetpath "(target) does not exist" 93 | exit 94 | fi 95 | 96 | #Exit when another backup is still running. 97 | if [ -f /tmp/cpsync.pid ]; then 98 | echo Another Sync job is running ... exiting 99 | exit 100 | fi 101 | #Create pid file 102 | echo $$ > /tmp/cpsync.pid 103 | 104 | trap cleanexit EXIT 105 | 106 | #Perform an extra check to work-around rapower triggering multiple usb_add scripts 107 | thissync=$(date +%s) 108 | if [ -f /tmp/cpsync.last ]; then 109 | lastsync=$(cat "/tmp/cpsync.last") 110 | delta=$((thissync-lastsync)) 111 | if [ $delta -lt 20 ]; then 112 | echo Can not sync again within 20 seconds to prevent multiple usb triggers 113 | exit 114 | fi 115 | fi 116 | 117 | # blink wifiled while busy. Back to normal after succes. Statusled keeps blinking after failure. 118 | # wifidg file stops OS from changing the leds. 119 | # Completion signal is questionable, since control is handed back to OS after this program ends. 120 | touch /var/lock/wifidg 121 | leds_active 122 | echo $thissync > "/tmp/cpsync.last" 123 | 124 | #Alle systems GO! 125 | 126 | #Set the source location and find or create unique ID for this source 127 | sourcepath=$(readlink -f "$sourcepath") 128 | uuid_file="$sourcepath/cardID.txt" 129 | if [ -e "$uuid_file" ]; then 130 | sd_uuid=$(cat "$uuid_file") 131 | else 132 | if [ -f /proc/sys/kernel/random/uuid ]; then 133 | sd_uuid=`cat /proc/sys/kernel/random/uuid` 134 | else 135 | sd_uuid="unknown" 136 | fi 137 | echo "$sd_uuid" > "$uuid_file" 138 | fi 139 | #Set the target location to include the ID 140 | targetpath=$(readlink -f "$targetpath")/$sd_uuid 141 | 142 | #Create a working directory for this programm in the target 143 | workdir="$targetpath/@Syncfiles" 144 | if [ ! -d "$workdir" ]; then 145 | mkdir -p "$workdir" 146 | fi 147 | 148 | echo Working directory is: $workdir 149 | tempfile="$workdir/synctemp.sd.txt" 150 | sourcesfile="$workdir/syncsources.sd.txt" 151 | targetsfile="$workdir/synctargets.sd.txt" 152 | archivepath="$targetpath/@Trashcan" 153 | todofile="$workdir/syncactions.txt" 154 | deletefile="$workdir/syncdeletes.txt" 155 | dirfile="$workdir/syncdirs.txt" 156 | logfile="$workdir/synclog.txt" 157 | 158 | 159 | echo starting 160 | # Set counters 161 | numdirs=0 162 | numfiles=0 163 | totfiles=0 164 | numdel=0 165 | numerr=0 166 | nummverr=0 167 | 168 | # Create support files or purge existing ones 169 | [ -f "$logfile" ] && rm "$logfile" 170 | [ -f "$tempfile" ] && rm "$tempfile" 171 | # Put header in files otherwise grep compare fails 172 | echo Comparefile > "$sourcesfile" 173 | echo Comparefile > "$targetsfile" 174 | 175 | echo Sync from $sourcepath to $targetpath started >> $logfile 176 | 177 | find "$sourcepath" -type d ! -path "$sourcepath/Share" ! -path "*/.*" >> "$tempfile" 178 | #format output by adding the full path and add a prefix to stop grep from matching substrings 179 | while read f 180 | do 181 | echo ">>>"${f#${sourcepath}} >> "$dirfile" 182 | done <"$tempfile" 183 | rm $tempfile 184 | 185 | #Get all filenames from source, but ignore minidlna Share directory and hidden files and directories. 186 | find "$sourcepath" -type f ! -path "$sourcepath/Share/*" ! -path "*/.*" >> "$tempfile" 187 | 188 | #format output by adding the full path and add a prefix to stop grep from matching substrings 189 | while read f 190 | do 191 | echo ">>>"${f#${sourcepath}} >> "$sourcesfile" 192 | done <"$tempfile" 193 | rm $tempfile 194 | 195 | #Get all filenames from source, but ignore special folders used by this programm and hidden files and directories. 196 | find "$targetpath" -type f ! -path "*/.*" ! -path "$workdir*" ! -path "$archivepath*" >> "$tempfile" 197 | 198 | #format output by adding the full path and add a prefix to stop grep from matching substrings 199 | while read f 200 | do 201 | echo ">>>"${f#${targetpath}} >> "$targetsfile" 202 | done <"$tempfile" 203 | 204 | #Got sources and targets.... now find differences 205 | /bin/grep -v -f "$targetsfile" "$sourcesfile" > "$todofile" 206 | /bin/grep -v -f "$sourcesfile" "$targetsfile" > "$deletefile" 207 | 208 | echo Files in target: $(wc -l "$targetsfile") >> $logfile 209 | echo Files in source: $(wc -l "$sourcesfile") >> $logfile 210 | echo Files to be copied: $(wc -l "$todofile") >> $logfile 211 | echo Files to be archived: $(wc -l "$deletefile") >> $logfile 212 | 213 | #start measuring the time and disk usage to calculate performance of file operations 214 | starttime=$(date +%s) 215 | startdiskusg=$(df -m "$targetpath" | grep -vE '^Filesystem' | awk '{ print $3 }') 216 | 217 | #Create directories that are in source but not in target. 218 | while read f 219 | do 220 | #first remove the prefix needed to stop grep from matching substrings 221 | makepath=$targetpath${f:3} 222 | if [ ! -d "$makepath" ]; then 223 | mkdir -p "$makepath" 224 | numdirs=$((numdirs+1)) 225 | err=$? 226 | if [ $err != 0 ]; then 227 | echo "Failed! error $err: $makepath could not be created" >> $logfile 228 | numerr=$((numerr+1)) 229 | else 230 | echo Succesfully created "$makepath" >> $logfile 231 | numfiles=$((numfiles+1)) 232 | fi 233 | fi 234 | done <$dirfile 235 | 236 | 237 | #Copy files that are in source but not in target. Will not overwrite any existing file. Not even if target is older or other file with same name. 238 | while read f 239 | do 240 | #first remove the prefix needed to stop grep from matching substrings 241 | f=${f:3} 242 | cp "$sourcepath$f" "$targetpath$f" 243 | err=$? 244 | if [ $err != 0 ]; then 245 | echo "Failed! error $err: $sourcepath$f could not be copied" >> $logfile 246 | numerr=$((numerr+1)) 247 | else 248 | echo Succesfully copied "$sourcepath$f" >> $logfile 249 | numfiles=$((numfiles+1)) 250 | fi 251 | done <$todofile 252 | 253 | #Move files that are nog longer in source from target to @Trashcan. Empty directories wil not be removed! 254 | while read f 255 | do 256 | #first remove the prefix needed to stop grep from matching substrings 257 | f=${f:3} 258 | echo Moving $f to @Trashcan 259 | makepath="$archivepath${f%/*}" 260 | if [ ! -d "$makepath" ]; then 261 | mkdir -p "$makepath" 262 | fi 263 | mv "$targetpath$f" "$archivepath$f" 264 | err=$? 265 | if [ $err != 0 ]; then 266 | echo "Failed! error $err moving $f to @Trashcan" >> $logfile 267 | nummverr=$((nummverr+1)) 268 | else 269 | echo Moved $f to @Trashcan >> $logfile 270 | numdel=$((numdel+1)) 271 | fi 272 | done <$deletefile 273 | 274 | #calculate time taken by actual sync 275 | endtime=$(date +%s) 276 | timespend=$((endtime-starttime)) 277 | #calculate changes en speed in MB 278 | enddiskusg=$(df -m "$targetpath" | grep -vE '^Filesystem' | awk '{ print $3 }') 279 | diskusgdelta=$((enddiskusg-startdiskusg)) 280 | [ $timespend -gt 0 ] && copyspeed=$((diskusgdelta/timespend)) || copyspeed="--" 281 | 282 | #create log 283 | echo Done creating $numdirs Directories en copying $numfiles Files. $numdel files where moved to @Trashcan. >> $logfile 284 | echo $numerr errors occurred while copying files. $nummverr errors while moving files to @Trashcan. >> $logfile 285 | echo Adding $diskusgdelta MB took: $timespend seconds at an avarage of $copyspeed MB/s. >> $logfile 286 | echo $(df -m "$targetpath" | grep -vE '^Filesystem' | awk '{ print $2 }') MB of space remaining on target drive. >> $logfile 287 | 288 | #signal success or failure through leds and eject source if parameter given 289 | if [ $((numerr+nummverr)) -eq 0 ]; then 290 | leds_done 291 | #Need a better way to umount. This does not remove mount directory. 292 | if [ "$3" == "eject" ]; then 293 | mountpoint=$(df "$sourcepath" | tail -1 | awk '{ print $6 }') 294 | /usr/sbin/umount2 "$mountpoint" 295 | [ $? != 0 ] && leds_error 296 | fi 297 | else 298 | leds_error 299 | fi 300 | #cleanup support files that where used to reduce the ram needed 301 | rm "$tempfile" 302 | rm "$sourcesfile" 303 | rm "$targetsfile" 304 | rm "$todofile" 305 | rm "$deletefile" 306 | rm "$dirfile" 307 | --------------------------------------------------------------------------------