forked from auanasgheps/snapraid-aio-script
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsnapraid-aio-script.sh
879 lines (796 loc) · 31.1 KB
/
snapraid-aio-script.sh
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
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
#!/bin/bash
########################################################################
# #
# Project page: https://github.com/auanasgheps/snapraid-aio-script #
# #
########################################################################
######################
# CONFIG VARIABLES #
######################
SNAPSCRIPTVERSION="3.1.DEV9"
# Read SnapRAID version
SNAPRAIDVERSION="$(snapraid -V | sed -e 's/snapraid v\(.*\)by.*/\1/')"
# find the current path
CURRENT_DIR=$(dirname "${0}")
# import the config file for this script which contain user configuration
CONFIG_FILE=$CURRENT_DIR/script-config.sh
#shellcheck source=script-config.sh
source "$CONFIG_FILE"
########################################################################
SYNC_MARKER="SYNC -"
SCRUB_MARKER="SCRUB -"
######################
# MAIN SCRIPT #
######################
function main(){
# create tmp file for output
true > "$TMP_OUTPUT"
# Redirect all output to file and screen. Starts a tee process
output_to_file_screen
# timestamp the job
echo "SnapRAID Script Job started [$(date)]"
echo "Running SnapRAID version $SNAPRAIDVERSION"
echo "SnapRAID AIO Script version $SNAPSCRIPTVERSION"
echo "----------------------------------------"
mklog "INFO: ----------------------------------------"
mklog "INFO: SnapRAID Script Job started"
mklog "INFO: Running SnapRAID version $SNAPRAIDVERSION"
mklog "INFO: SnapRAID Script version $SNAPSCRIPTVERSION"
echo "## Preprocessing"
# Initialize notification
if [ "$HEALTHCHECKS" -eq 1 ] || [ "$TELEGRAM" -eq 1 ] || [ "$DISCORD" -eq 1 ]; then
# install curl if not found
if [ "$(dpkg-query -W -f='${Status}' curl 2>/dev/null | grep -c "ok installed")" -eq 0 ]; then
echo "**Curl has not been found and will be installed.**"
mklog "WARN: Curl has not been found and will be installed."
# super silent and secret install command
export DEBIAN_FRONTEND=noninteractive
apt-get install -qq -o=Dpkg::Use-Pty=0 curl;
fi
# invoke notification services if configured
if [ "$HEALTHCHECKS" -eq 1 ]; then
echo "Healthchecks.io notification is enabled."
curl -fsS -m 5 --retry 3 -o /dev/null https://hc-ping.com/"$HEALTHCHECKS_ID"/start
fi
if [ "$TELEGRAM" -eq 1 ]; then
echo "Telegram notification is enabled."
curl -fsS -m 5 --retry 3 -o /dev/null -X POST \
-H 'Content-Type: application/json' \
-d '{"chat_id": "'$TELEGRAM_CHAT_ID'", "text": "SnapRAID Script Job started"}' \
https://api.telegram.org/bot"$TELEGRAM_TOKEN"/sendMessage
fi
if [ "$DISCORD" -eq 1 ]; then
echo "Discord notification is enabled."
curl -fsS -m 5 --retry 3 -o /dev/null -X POST \
-H 'Content-Type: application/json' \
-d '{"content": "SnapRAID Script Job started"}' \
"$DISCORD_WEBHOOK_URL"
fi
fi
# Check if script configuration file has been found, if not send a message
# to syslog and exit
if [ ! -f "$CONFIG_FILE" ]; then
echo "Script configuration file not found! The script cannot be run! Please check and try again!"
mklog_noconfig "WARN: Script configuration file not found! The script cannot be run! Please check and try again!"
exit 1;
# check if the config file has the correct version
elif [ "$CONFIG_VERSION" != 3.1 ]; then
echo "Please update your config file to the latest version. The current file is not compatible with this script!"
mklog "WARN: Please update your config file to the latest version. The current file is not compatible with this script!"
if [ "$EMAIL_ADDRESS" ]; then
SUBJECT="[WARNING] - Configuration Error $EMAIL_SUBJECT_PREFIX"
NOTIFY_OUTPUT="$SUBJECT"
trim_log < "$TMP_OUTPUT" | send_mail
notify_warning
fi
exit 1;
else
echo "Configuration file found."
mklog "INFO: Script configuration file found."
fi
# install markdown if not found
if [ "$(dpkg-query -W -f='${Status}' python3-markdown 2>/dev/null | grep -c "ok installed")" -eq 0 ]; then
echo "**Markdown has not been found and will be installed.**"
mklog "WARN: Markdown has not been found and will be installed."
# super silent and secret install command
export DEBIAN_FRONTEND=noninteractive
apt-get install -qq -o=Dpkg::Use-Pty=0 python3-markdown;
fi
# sanity check first to make sure we can access the content and parity files
mklog "INFO: Checking SnapRAID disks"
sanity_check
# pause configured containers
if [ "$MANAGE_SERVICES" -eq 1 ]; then
service_array_setup
if [ "$DOCKERALLOK" = YES ]; then
echo
if [ "$DOCKER_MODE" = 1 ]; then
echo "### Pausing Containers [$(date)]";
else
echo "### Stopping Containers [$(date)]";
fi
pause_services
fi
fi
# Custom Hook - Before
if [ "$CUSTOM_HOOK" -eq 1 ]; then
echo "### Custom Hook [$BEFORE_HOOK_NAME]";
bash -c "$BEFORE_HOOK_CMD"
fi
echo "----------------------------------------"
echo "## Processing"
# Fix timestamps
chk_zero
# run the snapraid DIFF command
echo "### SnapRAID DIFF [$(date)]"
mklog "INFO: SnapRAID DIFF started"
echo "\`\`\`"
$SNAPRAID_BIN diff
close_output_and_wait
output_to_file_screen
echo "\`\`\`"
echo "DIFF finished [$(date)]"
mklog "INFO: SnapRAID DIFF finished"
JOBS_DONE="DIFF"
# Get number of deleted, updated, and modified files...
get_counts
# sanity check to make sure that we were able to get our counts from the
# output of the DIFF job
if [ -z "$DEL_COUNT" ] || [ -z "$ADD_COUNT" ] || [ -z "$MOVE_COUNT" ] || [ -z "$COPY_COUNT" ] || [ -z "$UPDATE_COUNT" ]; then
# failed to get one or more of the count values, lets report to user and
# exit with error code
echo "**ERROR** - Failed to get one or more count values. Unable to continue."
mklog "WARN: Failed to get one or more count values. Unable to continue."
echo "Exiting script. [$(date)]"
if [ "$EMAIL_ADDRESS" ]; then
SUBJECT="[WARNING] - Unable to continue with SYNC/SCRUB job(s). Check DIFF job output. $EMAIL_SUBJECT_PREFIX"
NOTIFY_OUTPUT="$SUBJECT"
trim_log < "$TMP_OUTPUT" | send_mail
notify_warning
fi
exit 1;
fi
echo "**SUMMARY: Equal [$EQ_COUNT] - Added [$ADD_COUNT] - Deleted [$DEL_COUNT] - Moved [$MOVE_COUNT] - Copied [$COPY_COUNT] - Updated [$UPDATE_COUNT]**"
mklog "INFO: SUMMARY: Equal [$EQ_COUNT] - Added [$ADD_COUNT] - Deleted [$DEL_COUNT] - Moved [$MOVE_COUNT] - Copied [$COPY_COUNT] - Updated [$UPDATE_COUNT]"
# check if the conditions to run SYNC are met
# CHK 1 - if files have changed
if [ "$DEL_COUNT" -gt 0 ] || [ "$ADD_COUNT" -gt 0 ] || [ "$MOVE_COUNT" -gt 0 ] || [ "$COPY_COUNT" -gt 0 ] || [ "$UPDATE_COUNT" -gt 0 ]; then
chk_del
if [ "$CHK_FAIL" -eq 0 ]; then
chk_updated
fi
if [ "$CHK_FAIL" -eq 1 ]; then
chk_sync_warn
fi
else
# NO, so let's skip SYNC
echo "No change detected. Not running SYNC job. [$(date)]"
mklog "INFO: No change detected. Not running SYNC job."
DO_SYNC=0
fi
# Now run sync if conditions are met
if [ "$DO_SYNC" -eq 1 ]; then
echo "SYNC is authorized. [$(date)]"
echo "### SnapRAID SYNC [$(date)]"
mklog "INFO: SnapRAID SYNC Job started"
echo "\`\`\`"
if [ "$PREHASH" -eq 1 ]; then
$SNAPRAID_BIN sync -h -q
else
$SNAPRAID_BIN sync -q
fi
close_output_and_wait
output_to_file_screen
echo "\`\`\`"
echo "SYNC finished [$(date)]"
mklog "INFO: SnapRAID SYNC Job finished"
JOBS_DONE="$JOBS_DONE + SYNC"
# insert SYNC marker to 'Everything OK' or 'Nothing to do' string to
# differentiate it from SCRUB job later
sed_me "
s/^Everything OK/${SYNC_MARKER} Everything OK/g;
s/^Nothing to do/${SYNC_MARKER} Nothing to do/g" "$TMP_OUTPUT"
# Remove any warning flags if set previously. This is done in this step to
# take care of scenarios when user has manually synced or restored deleted
# files and we will have missed it in the checks above.
if [ -e "$SYNC_WARN_FILE" ]; then
rm "$SYNC_WARN_FILE"
fi
fi
# Moving onto scrub now. Check if user has enabled scrub
echo "### SnapRAID SCRUB [$(date)]"
mklog "INFO: SnapRAID SCRUB Job started"
if [ "$SCRUB_PERCENT" -gt 0 ]; then
# YES, first let's check if delete threshold has been breached and we have
# not forced a sync.
if [ "$CHK_FAIL" -eq 1 ] && [ "$DO_SYNC" -eq 0 ]; then
# YES, parity is out of sync so let's not run scrub job
echo "Parity info is out of sync (deleted or changed files threshold has been breached)."
echo "Not running SCRUB job. [$(date)]"
mklog "INFO: Parity info is out of sync (deleted or changed files threshold has been breached). Not running SCRUB job."
else
# NO, delete threshold has not been breached OR we forced a sync, but we
# have one last test - let's make sure if sync ran, it completed
# successfully (by checking for the marker text in the output).
if [ "$DO_SYNC" -eq 1 ] && ! grep -qw "$SYNC_MARKER" "$TMP_OUTPUT"; then
# Sync ran but did not complete successfully so lets not run scrub to
# be safe
echo "**WARNING** - check output of SYNC job. Could not detect marker."
echo "Not running SCRUB job. [$(date)]"
mklog "WARN: Check output of SYNC job. Could not detect marker. Not running SCRUB job."
else
# Everything ok - ready to run the scrub job!
# The fuction will check if scrub delayed run is enabled and run scrub
# based on configured conditions
chk_scrub_settings
fi
fi
else
echo "Scrub job is not enabled. "
echo "Not running SCRUB job. [$(date)]"
mklog "INFO: Scrub job is not enabled. Not running SCRUB job."
fi
echo "----------------------------------------"
echo "## Postprocessing"
# Show SnapRAID SMART info if enabled
if [ "$SMART_LOG" -eq 1 ]; then
echo "### SnapRAID Smart"
echo "\`\`\`"
$SNAPRAID_BIN smart
close_output_and_wait
output_to_file_screen
echo "\`\`\`"
fi
# Show SnapRAID Status information if enabled
if [ "$SNAP_STATUS" -eq 1 ]; then
echo "### SnapRAID Status"
echo "\`\`\`"
$SNAPRAID_BIN status
close_output_and_wait
output_to_file_screen
echo "\`\`\`"
fi
# Spinning down disks (Method 1: snapraid - preferred)
# if [ "$SPINDOWN" -eq 1 ]; then
# echo "### SnapRAID Spindown"
# echo "\`\`\`"
# $SNAPRAID_BIN down
# close_output_and_wait
# output_to_file_screen
# echo "\`\`\`"
#fi
# Spinning down disks (Method 2: hdparm - spins down all rotational devices)
# if [ $SPINDOWN -eq 1 ]; then
# for DRIVE in `lsblk -d -o name | tail -n +2`
# do
# if [[ `smartctl -a /dev/$DRIVE | grep 'Rotation Rate' | grep rpm` ]]; then
# hdparm -Y /dev/$DRIVE
# fi
# done
# fi
### Spinning down disks (Method 3: hd-idle - spins down all rotational devices)
if [ "$SPINDOWN" -eq 1 ]; then
for DRIVE in $(lsblk -d -o name | tail -n +2)
do
if [[ $(smartctl -a /dev/"$DRIVE" | grep 'Rotation Rate' | grep rpm) ]]; then
echo "spinning down /dev/$DRIVE"
hd-idle -t /dev/"$DRIVE"
fi
done
fi
# Resume paused containers
if [ "$SERVICES_STOPPED" -eq 1 ]; then
echo
if [ "$DOCKER_MODE" = 1 ]; then
echo "### Resuming Containers [$(date)]";
else
echo "### Restarting Containers [$(date)]";
fi
resume_services
fi
# Custom Hook - After
if [ "$CUSTOM_HOOK" -eq 1 ]; then
echo "### Custom Hook - [$AFTER_HOOK_NAME]";
bash -c "$AFTER_HOOK_CMD"
fi
echo "All jobs ended. [$(date)]"
mklog "INFO: Snapraid: all jobs ended."
# all jobs done, let's send output to user if configured
if [ "$EMAIL_ADDRESS" ] || [ -x "$HOOK_NOTIFICATION" ]; then
# check snapraid output and build the message subject, then send notifications if enabled
prepare_mail
ELAPSED="$((SECONDS / 3600))hrs $(((SECONDS / 60) % 60))min $((SECONDS % 60))sec"
echo "----------------------------------------"
echo "## Total time elapsed for SnapRAID: $ELAPSED"
mklog "INFO: Total time elapsed for SnapRAID: $ELAPSED"
# Add a topline to email body
sed_me "1s:^:##$SUBJECT \n:" "${TMP_OUTPUT}"
if [ "$VERBOSITY" -eq 1 ]; then
send_mail < "$TMP_OUTPUT"
else
trim_log < "$TMP_OUTPUT" | send_mail
fi
fi
# exit with success, letting the trap handle cleanup of file descriptors
exit 0;
}
#######################
# FUNCTIONS & METHODS #
#######################
function sanity_check() {
echo "Checking if all parity and content files are present."
mklog "INFO: Checking if all parity and content files are present."
for i in "${PARITY_FILES[@]}"; do
if [ ! -e "$i" ]; then
echo "[$(date)] ERROR - Parity file ($i) not found!"
echo "ERROR - Parity file ($i) not found!" >> "$TMP_OUTPUT"
echo "**ERROR**: Please check the status of your disks! The script exits here due to missing file or disk."
mklog "WARN: Parity file ($i) not found!"
mklog "WARN: Please check the status of your disks! The script exits here due to missing file or disk."
# Add a topline to email body
SUBJECT="[WARNING] - Parity file ($i) not found! $EMAIL_SUBJECT_PREFIX"
NOTIFY_OUTPUT="$SUBJECT"
trim_log < "$TMP_OUTPUT" | send_mail
notify_warning
exit 1;
fi
done
echo "All parity files found."
mklog "INFO: All parity files found."
for i in "${CONTENT_FILES[@]}"; do
if [ ! -e "$i" ]; then
echo "[$(date)] ERROR - Content file ($i) not found!"
echo "ERROR - Content file ($i) not found!" >> "$TMP_OUTPUT"
echo "**ERROR**: Please check the status of your disks! The script exits here due to missing file or disk."
mklog "WARN: Content file ($i) not found!"
mklog "WARN: Please check the status of your disks! The script exits here due to missing file or disk."
# Add a topline to email body
SUBJECT="[WARNING] - Content file ($i) not found! $EMAIL_SUBJECT_PREFIX"
NOTIFY_OUTPUT="$SUBJECT"
trim_log < "$TMP_OUTPUT" | send_mail
notify_warning
exit 1;
fi
done
echo "All content files found."
mklog "INFO: All content files found."
}
function get_counts() {
EQ_COUNT=$(grep -w '^ \{1,\}[0-9]* equal' "$TMP_OUTPUT" | sed 's/^ *//g' | cut -d ' ' -f1)
ADD_COUNT=$(grep -w '^ \{1,\}[0-9]* added' "$TMP_OUTPUT" | sed 's/^ *//g' | cut -d ' ' -f1)
DEL_COUNT=$(grep -w '^ \{1,\}[0-9]* removed' "$TMP_OUTPUT" | sed 's/^ *//g' | cut -d ' ' -f1)
UPDATE_COUNT=$(grep -w '^ \{1,\}[0-9]* updated' "$TMP_OUTPUT" | sed 's/^ *//g' | cut -d ' ' -f1)
MOVE_COUNT=$(grep -w '^ \{1,\}[0-9]* moved' "$TMP_OUTPUT" | sed 's/^ *//g' | cut -d ' ' -f1)
COPY_COUNT=$(grep -w '^ \{1,\}[0-9]* copied' "$TMP_OUTPUT" | sed 's/^ *//g' | cut -d ' ' -f1)
# REST_COUNT=$(grep -w '^ \{1,\}[0-9]* restored' $TMP_OUTPUT | sed 's/^ *//g' | cut -d ' ' -f1)
}
function sed_me(){
# Close the open output stream first, then perform sed and open a new tee
# process and redirect output. We close stream because of the calls to new
# wait function in between sed_me calls. If we do not do this we try to close
# Processes which are not parents of the shell.
exec >& "$OUT" 2>& "$ERROR"
sed -i "$1" "$2"
output_to_file_screen
}
function chk_del(){
if [ "$DEL_COUNT" -lt "$DEL_THRESHOLD" ]; then
if [ "$DEL_COUNT" -eq 0 ]; then
echo "There are no deleted files, that's fine."
DO_SYNC=1
else
echo "There are deleted files. The number of deleted files ($DEL_COUNT) is below the threshold of ($DEL_THRESHOLD)."
DO_SYNC=1
fi
else
echo "**WARNING** Deleted files ($DEL_COUNT) reached/exceeded threshold ($DEL_THRESHOLD)."
mklog "WARN: Deleted files ($DEL_COUNT) reached/exceeded threshold ($DEL_THRESHOLD)."
CHK_FAIL=1
fi
}
function chk_updated(){
if [ "$UPDATE_COUNT" -lt "$UP_THRESHOLD" ]; then
if [ "$UPDATE_COUNT" -eq 0 ]; then
echo "There are no updated files, that's fine."
DO_SYNC=1
else
echo "There are updated files. The number of updated files ($UPDATE_COUNT) is below the threshold of ($UP_THRESHOLD)."
DO_SYNC=1
fi
else
echo "**WARNING** Updated files ($UPDATE_COUNT) reached/exceeded threshold ($UP_THRESHOLD)."
mklog "WARN: Updated files ($UPDATE_COUNT) reached/exceeded threshold ($UP_THRESHOLD)."
CHK_FAIL=1
fi
}
function chk_sync_warn(){
if [ "$SYNC_WARN_THRESHOLD" -gt -1 ]; then
if [ "$SYNC_WARN_THRESHOLD" -eq 0 ]; then
echo "Forced sync is enabled."
mklog "INFO: Forced sync is enabled."
else
echo "Sync after threshold warning(s) is enabled."
mklog "INFO: Sync after threshold warning(s) is enabled."
fi
local sync_warn_count
sync_warn_count=$(sed '/^[0-9]*$/!d' "$SYNC_WARN_FILE" 2>/dev/null)
# zero if file does not exist or did not contain a number
: "${sync_warn_count:=0}"
if [ "$sync_warn_count" -ge "$SYNC_WARN_THRESHOLD" ]; then
# Force a sync. If the warn count is zero it means the sync was already
# forced, do not output a dumb message and continue with the sync job.
if [ "$sync_warn_count" -eq 0 ]; then
DO_SYNC=1
else
# If there is at least one warn count, output a message and force a
# sync job. Do not need to remove warning marker here as it is
# automatically removed when the sync job is run by this script
echo "Number of threshold warning(s) ($sync_warn_count) has reached/exceeded threshold ($SYNC_WARN_THRESHOLD). Forcing a SYNC job to run."
mklog "INFO: Number of threshold warning(s) ($sync_warn_count) has reached/exceeded threshold ($SYNC_WARN_THRESHOLD). Forcing a SYNC job to run."
DO_SYNC=1
fi
else
# NO, so let's increment the warning count and skip the sync job
((sync_warn_count += 1))
echo "$sync_warn_count" > "$SYNC_WARN_FILE"
if [ "$sync_warn_count" == "$SYNC_WARN_THRESHOLD" ]; then
echo "This is the **last** warning left. **NOT** proceeding with SYNC job. [$(date)]"
mklog "INFO: This is the **last** warning left. **NOT** proceeding with SYNC job. [$(date)]"
DO_SYNC=0
else
echo "$((SYNC_WARN_THRESHOLD - sync_warn_count)) threshold warning(s) until the next forced sync. **NOT** proceeding with SYNC job. [$(date)]"
mklog "INFO: $((SYNC_WARN_THRESHOLD - sync_warn_count)) threshold warning(s) until the next forced sync. **NOT** proceeding with SYNC job."
DO_SYNC=0
fi
fi
else
# NO, so let's skip SYNC
echo "Forced sync is not enabled. Check $TMP_OUTPUT for details. **NOT** proceeding with SYNC job. [$(date)]"
mklog "INFO: Forced sync is not enabled. Check $TMP_OUTPUT for details. **NOT** proceeding with SYNC job."
DO_SYNC=0
fi
}
function chk_zero(){
echo "### SnapRAID TOUCH [$(date)]"
echo "Checking for zero sub-second files."
TIMESTATUS=$($SNAPRAID_BIN status | grep 'You have [1-9][0-9]* files with zero sub-second timestamp\.' | sed 's/^You have/Found/g')
if [ -n "$TIMESTATUS" ]; then
echo "$TIMESTATUS"
echo "Running TOUCH job to timestamp. [$(date)]"
echo "\`\`\`"
$SNAPRAID_BIN touch
close_output_and_wait
output_to_file_screen
echo "\`\`\`"
else
echo "No zero sub-second timestamp files found."
fi
echo "TOUCH finished [$(date)]"
}
function chk_scrub_settings(){
if [ "$SCRUB_DELAYED_RUN" -gt 0 ]; then
echo "Delayed scrub is enabled."
mklog "INFO: Delayed scrub is enabled.."
fi
local scrub_count
scrub_count=$(sed '/^[0-9]*$/!d' "$SCRUB_COUNT_FILE" 2>/dev/null)
# zero if file does not exist or did not contain a number
: "${scrub_count:=0}"
if [ "$scrub_count" -ge "$SCRUB_DELAYED_RUN" ]; then
# Run a scrub job. if the warn count is zero it means the scrub was already
# forced, do not output a dumb message and continue with the scrub job.
if [ "$scrub_count" -eq 0 ]; then
echo
run_scrub
else
# if there is at least one warn count, output a message and force a scrub
# job. Do not need to remove warning marker here as it is automatically
# removed when the scrub job is run by this script
echo "Number of delayed runs has reached/exceeded threshold ($SCRUB_DELAYED_RUN). A SCRUB job will run."
mklog "INFO: Number of delayed runs has reached/exceeded threshold ($SCRUB_DELAYED_RUN). A SCRUB job will run."
echo
run_scrub
fi
else
# NO, so let's increment the warning count and skip the scrub job
((scrub_count += 1))
echo "$scrub_count" > "$SCRUB_COUNT_FILE"
if [ "$scrub_count" == "$SCRUB_DELAYED_RUN" ]; then
echo "This is the **last** run left before running scrub job next time. [$(date)]"
mklog "INFO: This is the **last** run left before running scrub job next time. [$(date)]"
else
echo "$((SCRUB_DELAYED_RUN - scrub_count)) runs until the next scrub. **NOT** proceeding with SCRUB job. [$(date)]"
mklog "INFO: $((SCRUB_DELAYED_RUN - scrub_count)) runs until the next scrub. **NOT** proceeding with SCRUB job. [$(date)]"
fi
fi
}
function run_scrub(){
echo "\`\`\`"
$SNAPRAID_BIN scrub -p "$SCRUB_PERCENT" -o "$SCRUB_AGE" -q
close_output_and_wait
output_to_file_screen
echo "\`\`\`"
echo "SCRUB finished [$(date)]"
mklog "INFO: SnapRAID SCRUB Job finished"
JOBS_DONE="$JOBS_DONE + SCRUB"
# insert SCRUB marker to 'Everything OK' or 'Nothing to do' string to
# differentiate it from SYNC job above
sed_me "
s/^Everything OK/${SCRUB_MARKER} Everything OK/g;
s/^Nothing to do/${SCRUB_MARKER} Nothing to do/g" "$TMP_OUTPUT"
# Remove the warning flag if set previously. This is done now to
# take care of scenarios when user has manually synced or restored
# deleted files and we will have missed it in the checks above.
if [ -e "$SCRUB_COUNT_FILE" ]; then
rm "$SCRUB_COUNT_FILE"
fi
}
function service_array_setup() {
# check if container names are set correctly
if [ -z "$SERVICES" ] && [ -z "$DOCKER_HOST_SERVICES" ]; then
echo "Please configure Containers. Unable to manage containers."
ARRAY_VALIDATED=NO
else
echo "Docker containers management is enabled."
ARRAY_VALIDATED=YES
fi
# check what docker mode is set
if [ "$DOCKER_MODE" = 1 ]; then
DOCKER_CMD1=pause
DOCKER_CMD2=unpause
DOCKERCMD_VALIDATED=YES
elif [ "$DOCKER_MODE" = 2 ]; then
DOCKER_CMD1=stop
DOCKER_CMD2=start
DOCKERCMD_VALIDATED=YES
else
echo "Please check your command configuration. Unable to manage containers."
DOCKERCMD_VALIDATED=NO
fi
# validate docker configuration
if [ "$ARRAY_VALIDATED" = YES ] && [ "$DOCKERCMD_VALIDATED" = YES ]; then
DOCKERALLOK=YES
else
DOCKERALLOK=NO
fi
}
function pause_services(){
if [ "$DOCKER_REMOTE" -eq 1 ]; then
for i in "${DOCKER_HOST_SERVICES[@]}"; do
# delete previous array/list (this is crucial!)
unset remote_service_array
# split sub-list if available
if [[ $i == *":"* ]]
then
# split host name from services
tmpArray=(${i//:/ })
REMOTE_HOST=${tmpArray[0]}
REMOTE_SERVICES=${tmpArray[1]}
fi
# make array from simple string
IFS=',' read -r -a remote_service_array <<<"$REMOTE_SERVICES"
# Loop over services
for j in "${remote_service_array[@]}"; do
if [ "$DOCKER_MODE" = 1 ]; then
echo "Pausing Container - ""${j^}";
else
echo "Stopping Container - ""${j^}";
fi
ssh "$DOCKER_USER"@"$REMOTE_HOST" docker "$DOCKER_CMD1" "$j"
sleep "$DOCKER_DELAY"
done
done
else
IFS=' ' read -r -a service_array <<<"$SERVICES"
for i in "${service_array[@]}"; do
if [ "$DOCKER_MODE" = 1 ]; then
echo "Pausing Container - ""${i^}";
else
echo "Stopping Container - ""${i^}";
fi
docker "$DOCKER_CMD1" "$i"
done
fi
SERVICES_STOPPED=1
unset IFS
}
function resume_services(){
if [ "$SERVICES_STOPPED" -eq 1 ]; then
if [ "$DOCKER_REMOTE" -eq 1 ]; then
for i in "${DOCKER_HOST_SERVICES[@]}"; do
# delete previous array/list (this is crucial!)
unset remote_service_array
# split sub-list if available
if [[ $i == *":"* ]]
then
# split host name from services
tmpArray=(${i//:/ })
REMOTE_HOST=${tmpArray[0]}
REMOTE_SERVICES=${tmpArray[1]}
fi
# make array from simple string
IFS=',' read -r -a remote_service_array <<<"$REMOTE_SERVICES"
# Loop over services
for j in "${remote_service_array[@]}"; do
if [ "$DOCKER_MODE" = 1 ]; then
echo "Resuming Container - ""${j^}";
else
echo "Restarting Container - ""${j^}";
fi
ssh "$DOCKER_USER"@"$REMOTE_HOST" docker "$DOCKER_CMD2" "$j"
sleep "$DOCKER_DELAY"
done
done
else
IFS=' ' read -r -a service_array <<<"$SERVICES"
for i in "${service_array[@]}"; do
if [ "$DOCKER_MODE" = 1 ]; then
echo "Resuming Container - ""${i^}";
else
echo "Restarting Container - ""${i^}";
fi
docker "$DOCKER_CMD2" "$i"
done
fi
SERVICES_STOPPED=0
unset IFS
fi
}
function clean_desc(){
[[ $- == *i* ]] && exec &>/dev/tty
}
function final_cleanup(){
resume_services
clean_desc
exit
}
function prepare_mail() {
if [ $CHK_FAIL -eq 1 ]; then
if [ "$DEL_COUNT" -ge "$DEL_THRESHOLD" ] && [ "$DO_SYNC" -eq 0 ]; then
MSG="Deleted files ($DEL_COUNT) / ($DEL_THRESHOLD) violation"
fi
if [ "$DEL_COUNT" -ge "$DEL_THRESHOLD" ] && [ "$DO_SYNC" -eq 1 ]; then
MSG="Forced sync with deleted files ($DEL_COUNT) / ($DEL_THRESHOLD) violation"
fi
if [ "$UPDATE_COUNT" -ge "$UP_THRESHOLD" ] && [ "$DO_SYNC" -eq 0 ]; then
MSG="Changed files ($UPDATE_COUNT) / ($UP_THRESHOLD) violation"
fi
if [ "$UPDATE_COUNT" -ge "$UP_THRESHOLD" ] && [ "$DO_SYNC" -eq 1 ]; then
MSG="Forced sync with changed files ($UPDATE_COUNT) / ($UP_THRESHOLD) violation"
fi
if [ "$DEL_COUNT" -ge "$DEL_THRESHOLD" ] && [ "$UPDATE_COUNT" -ge "$UP_THRESHOLD" ] && [ "$DO_SYNC" -eq 0 ]; then
MSG="Multiple violations - Deleted files ($DEL_COUNT) / ($DEL_THRESHOLD) and changed files ($UPDATE_COUNT) / ($UP_THRESHOLD)"
fi
if [ "$DEL_COUNT" -ge "$DEL_THRESHOLD" ] && [ "$UPDATE_COUNT" -ge "$UP_THRESHOLD" ] && [ "$DO_SYNC" -eq 1 ]; then
MSG="Sync forced with multiple violations - Deleted files ($DEL_COUNT) / ($DEL_THRESHOLD) and changed files ($UPDATE_COUNT) / ($UP_THRESHOLD)"
fi
SUBJECT="[WARNING] $MSG $EMAIL_SUBJECT_PREFIX"
NOTIFY_OUTPUT="$SUBJECT"
notify_warning
elif [ -z "${JOBS_DONE##*"SYNC"*}" ] && ! grep -qw "$SYNC_MARKER" "$TMP_OUTPUT"; then
# Sync ran but did not complete successfully so lets warn the user
SUBJECT="[WARNING] SYNC job ran but did not complete successfully $EMAIL_SUBJECT_PREFIX"
NOTIFY_OUTPUT="$SUBJECT"
notify_warning
elif [ -z "${JOBS_DONE##*"SCRUB"*}" ] && ! grep -qw "$SCRUB_MARKER" "$TMP_OUTPUT"; then
# Scrub ran but did not complete successfully so lets warn the user
SUBJECT="[WARNING] SCRUB job ran but did not complete successfully $EMAIL_SUBJECT_PREFIX"
NOTIFY_OUTPUT="$SUBJECT
SUMMARY: Equal [$EQ_COUNT] - Added [$ADD_COUNT] - Deleted [$DEL_COUNT] - Moved [$MOVE_COUNT] - Copied [$COPY_COUNT] - Updated [$UPDATE_COUNT]"
notify_warning
else
SUBJECT="[COMPLETED] $JOBS_DONE Jobs $EMAIL_SUBJECT_PREFIX"
NOTIFY_OUTPUT="$SUBJECT
SUMMARY: Equal [$EQ_COUNT] - Added [$ADD_COUNT] - Deleted [$DEL_COUNT] - Moved [$MOVE_COUNT] - Copied [$COPY_COUNT] - Updated [$UPDATE_COUNT]"
notify_success
fi
}
function notify_success(){
if [ "$HEALTHCHECKS" -eq 1 ]; then
curl -fsS -m 5 --retry 3 -o /dev/null https://hc-ping.com/"$HEALTHCHECKS_ID"/0 --data-raw "$NOTIFY_OUTPUT"
fi
if [ "$TELEGRAM" -eq 1 ]; then
curl -fsS -m 5 --retry 3 -o /dev/null -X POST \
-H 'Content-Type: application/json' \
-d '{"chat_id": "'"$TELEGRAM_CHAT_ID"'", "text": "'"$NOTIFY_OUTPUT"'"}' \
https://api.telegram.org/bot"$TELEGRAM_TOKEN"/sendMessage
fi
if [ "$DISCORD" -eq 1 ]; then
curl -fsS -m 5 --retry 3 -o /dev/null -X POST \
-H 'Content-Type: application/json' \
-d '{"content": "'"$SUBJECT"'"}' \
"$DISCORD_WEBHOOK_URL"
fi
}
function notify_warning(){
if [ "$HEALTHCHECKS" -eq 1 ]; then
curl -fsS -m 5 --retry 3 -o /dev/null https://hc-ping.com/"$HEALTHCHECKS_ID"/fail --data-raw "$NOTIFY_OUTPUT"
fi
if [ "$TELEGRAM" -eq 1 ]; then
curl -fsS -m 5 --retry 3 -o /dev/null -X POST \
-H 'Content-Type: application/json' \
-d '{"chat_id": "'"$TELEGRAM_CHAT_ID"'", "text": "'"$NOTIFY_OUTPUT"'"}' \
https://api.telegram.org/bot"$TELEGRAM_TOKEN"/sendMessage
fi
if [ "$DISCORD" -eq 1 ]; then
curl -fsS -m 5 --retry 3 -o /dev/null -X POST \
-H 'Content-Type: application/json' \
-d '{"content": "'"$SUBJECT"'"}' \
"$DISCORD_WEBHOOK_URL"
fi
}
# Trim the log file read from stdin.
function trim_log(){
sed '
/^Running TOUCH job to timestamp/,/^\TOUCH finished/{
/^Running TOUCH job to timestamp/!{/^TOUCH finished/!d}
};
/^### SnapRAID DIFF/,/^\DIFF finished/{
/^### SnapRAID DIFF/!{/^DIFF finished/!d}
}'
}
# Process and mail the email body read from stdin.
function send_mail(){
local body; body=$(cat)
# Send the raw $body and append the HTML.
# Try to workaround py markdown 2.6.8 issues:
# 1. Will not format code blocks with empty lines, so just remove
# them.
# 2. A dash line inside of code block brekas it, so remove it.
# 3. Add trailing double-spaces ensures the line endings are
# maintained.
# 4. The HTML code blocks need to be modified to use <pre></pre> to display
# correctly.
body=$(echo "$body" | sed '/^[[:space:]]*$/d; /^ -*$/d; s/$/ /' |
python3 -m markdown |
sed 's/<code>/<pre>/;s%</code>%</pre>%')
if [ -x "$HOOK_NOTIFICATION" ]; then
echo -e "Notification user script is set. Calling it now [$(date)]"
$HOOK_NOTIFICATION "$SUBJECT" "$body"
else
echo -e "Email address is set. Sending email report to **$EMAIL_ADDRESS** [$(date)]"
$MAIL_BIN -a 'Content-Type: text/html' -s "$SUBJECT" -r "$FROM_EMAIL_ADDRESS" "$EMAIL_ADDRESS" \
< <(echo "$body")
fi
}
# Due to how process substitution and newer bash versions work, this function
# stops the output stream which allows wait stops wait from hanging on the tee
# process. If we do not do this and use normal 'wait' the processes will wait
# forever as newer bash version will wait for the process substitution to
# finish. Probably not the best way of 'fixing' this issue. Someone with more
# knowledge can provide better insight.
function close_output_and_wait(){
exec >& "$OUT" 2>& "$ERROR"
CHILD_PID=$(pgrep -P $$)
if [ -n "$CHILD_PID" ]; then
wait "$CHILD_PID"
fi
}
# Redirects output to file and screen. Open a new tee process.
function output_to_file_screen(){
# redirect all output to screen and file
exec {OUT}>&1 {ERROR}>&2
# NOTE: Not preferred format but valid: exec &> >(tee -ia "${TMP_OUTPUT}" )
exec > >(tee -a "${TMP_OUTPUT}") 2>&1
}
# Sends important messages to syslog
function mklog() {
[[ "$*" =~ ^([A-Za-z]*):\ (.*) ]] &&
{
PRIORITY=${BASH_REMATCH[1]} # INFO, DEBUG, WARN
LOGMESSAGE=${BASH_REMATCH[2]} # the Log-Message
}
echo "$(date '+[%Y-%m-%d %H:%M:%S]') $(basename "$0"): $PRIORITY: '$LOGMESSAGE'" >> "$SNAPRAID_LOG"
}
# Emergency syslog function when no config is found, using default log location
function mklog_noconfig() {
[[ "$*" =~ ^([A-Za-z]*):\ (.*) ]] &&
{
PRIORITY=${BASH_REMATCH[1]} # INFO, DEBUG, WARN
LOGMESSAGE=${BASH_REMATCH[2]} # the Log-Message
}
echo "$(date '+[%Y-%m-%d %H:%M:%S]') $(basename "$0"): $PRIORITY: '$LOGMESSAGE'" >> "/var/log/snapraid.log"
}
# Set TRAP
trap final_cleanup INT EXIT
main "$@"