2 # Desc: Compresses, encrypts, and writes stdin every 5 seconds
4 #==BEGIN Define script parameters==
5 #===BEGIN Initialize variables===
7 # Logging Behavior parameters
8 bufferTTL
="300"; # Time-to-live (seconds) for each buffer round
9 scriptTTL_TE
="day"; # Time element at the end of which script terminates
10 dirTmpDefault
="/dev/shm"; # Default parent of working directory
13 scriptName
="bklog"; # Define basename of script file.
14 scriptVersion
="0.1.19"; # Define version of script.
15 scriptURL
="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script.
16 scriptTimeStart
="$(date +%Y%m%dT%H%M%S.%N)"; # YYYYmmddTHHMMSS.NNNNNNNNN
17 scriptHostname
=$
(hostname
); # Save hostname of system running this script.
18 PATH
="$HOME/.local/bin:$PATH"; # Add "$(systemd-path user-binaries)" path in case user apps saved there
19 ageVersion
="1.0.0-beta2"; # Define version of age (encryption program)
20 ageURL
="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age.
23 declare -a buffer
# array for storing while read buffer
24 declare -a argRecPubKeys
# array for processArguments function
25 declare -a recPubKeysValid
# array for storing both '-r' and '-R' recipient pubkeys
26 declare -a argProcStrings argProcFileExts
# for storing buffer processing strings (ex: "gpsbabel -i nmea -f - -o gpx -F - ")
27 declare -Ag appRollCall
# Associative array for storing app status
28 declare -Ag fileRollCall
# Associative array for storing file status
29 declare -Ag dirRollCall
# Associative array for storing dir status
30 declare -a procStrings procFileExts
# Arrays for storing processing commands and resulting output file extensions
33 optionVerbose
=""; optionEncrypt
=""; dirOut
=""; optionEncrypt
=""; dir_tmp
="";
34 cmd_compress
="";cmd_compress_suffix
=""; cmd_encrypt
=""; cmd_encrypt_suffix
="";
36 #===END Initialize variables===
38 #===BEGIN Declare local script functions===
39 yell
() { echo "$0: $*" >&2; } #o Yell, Die, Try Three-Fingered Claw technique
40 die
() { yell
"$*"; exit 111; } #o Ref/Attrib: https://stackoverflow.com/a/25515370
41 try
() { "$@" || die
"cannot $*"; } #o
43 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
45 -v |
--verbose) optionVerbose
="true"; vbm
"DEBUG :Verbose mode enabled.";; # Enable verbose mode.
46 -h |
--help) showUsage
; exit 1;; # Display usage.
47 --version) showVersion
; exit 1;; # Show version
48 -o |
--output) if [ -d "$2" ]; then dirOut
="$2"; vbm
"DEBUG :dirOut:$dirOut"; shift; fi ;; # Define output directory.
49 -e |
--encrypt) optionEncrypt
="true"; vbm
"DEBUG :Encrypted output mode enabled.";; # Enable encryption
50 -r |
--recipient) optionRecArg
="true"; argRecPubKeys
+=("$2"); vbm
"STATUS:pubkey added:""$2"; shift;; # Add recipients
51 -R |
--recipient-dir) optionRecDir
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir
52 -c |
--compress) optionCompress
="true"; vbm
"DEBUG :Compressed output mode enabled.";; # Enable compression
53 -z |
--time-zone) try setTimeZoneEV
"$2"; shift;; # Set timestamp timezone
54 -t |
--temp-dir) optionTmpDir
="true" && argTempDirPriority
="$2"; shift;; # Set time zone
55 -b |
--buffer-ttl) optionCustomBufferTTL
="true" && argCustomBufferTTL
="$2"; shift;; # Set custom buffer period (default: 300 seconds)
56 -B |
--script-ttl) optionCustomScriptTTL_TE
="true" && argCustomScriptTTL_TE
="$2"; shift;; # Set custom script TTL (default: "day")
57 -p |
--process-string) optionProcString
="true" && argProcStrings
+=("$2") && argProcFileExts
+=("$3") && vbm
"STATUS:file extension \"$2\" for output of processing string added:\"$3\""; shift; shift;;
58 -l |
--label) optionLabel
="true" && argLabel
="$2"; vbm
"DEBUG :Custom label received:$argLabel"; shift;;
59 -w |
--store-raw) optionStoreRaw
="true" && argRawFileExt
="$2"; vbm
"DEBUG :Raw stdin file extension received:$argRawFileExt"; shift;;
60 -W |
--no-store-raw) optionNoStoreRaw
="true"; vbm
"DEBUG :Option selected to not store raw stdin data."; shift;;
61 *) yell
"ERROR: Unrecognized argument: $1"; yell
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
65 } # Argument Processing
67 # Description: Prints verbose message ("vbm") to stderr if optionVerbose is set to "true".
68 # Usage: vbm "DEBUG :verbose message here"
73 # Depends: bash 5.0.3, echo 8.30, date 8.30
75 if [ "$optionVerbose" = "true" ]; then
76 functionTime
=$
(date --iso-8601=ns
); # Save current time in nano seconds.
77 echo "[$functionTime] ""$*" 1>&2; # Display argument text.
81 return 0; # Function finished.
82 } # Displays message if optionVerbose true
84 # Desc: If arg is a command, save result in assoc array 'appRollCall'
85 # Usage: checkapp arg1 arg2 arg3 ...
87 # Input: global assoc. array 'appRollCall'
88 # Output: adds/updates key(value) to global assoc array 'appRollCall'
94 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
95 appRollCall
[$arg]="true";
96 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
98 appRollCall
[$arg]="false"; returnState
="false";
102 #===Determine function return code===
103 if [ "$returnState" = "true" ]; then
108 } # Check that app exists
110 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
111 # Usage: checkfile arg1 arg2 arg3 ...
113 # Input: global assoc. array 'fileRollCall'
114 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
115 # Output: returns 0 if app found, 1 otherwise
116 # Depends: bash 5.0.3
121 if [ -f "$arg" ]; then
122 fileRollCall
["$arg"]="true";
123 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
125 fileRollCall
["$arg"]="false"; returnState
="false";
129 #===Determine function return code===
130 if [ "$returnState" = "true" ]; then
135 } # Check that file exists
137 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
138 # Usage: checkdir arg1 arg2 arg3 ...
140 # Input: global assoc. array 'dirRollCall'
141 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
142 # Output: returns 0 if app found, 1 otherwise
143 # Depends: Bash 5.0.3
148 if [ -d "$arg" ]; then
149 dirRollCall
["$arg"]="true";
150 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
152 dirRollCall
["$arg"]="false"; returnState
="false";
156 #===Determine function return code===
157 if [ "$returnState" = "true" ]; then
162 } # Check that dir exists
164 # Desc: Displays missing apps, files, and dirs
165 # Usage: displayMissing
167 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
168 # Output: stderr: messages indicating missing apps, file, or dirs
169 # Depends: bash 5, checkAppFileDir()
170 local missingApps value appMissing missingFiles fileMissing
171 local missingDirs dirMissing
173 #==BEGIN Display errors==
174 #===BEGIN Display Missing Apps===
175 missingApps
="Missing apps :";
176 #for key in "${!appRollCall[@]}"; do echo "DEBUG :$key => ${appRollCall[$key]}"; done
177 for key
in "${!appRollCall[@]}"; do
178 value
="${appRollCall[$key]}";
179 if [ "$value" = "false" ]; then
180 #echo "DEBUG :Missing apps: $key => $value";
181 missingApps
="$missingApps""$key ";
185 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
186 echo "$missingApps" 1>&2;
189 #===END Display Missing Apps===
191 #===BEGIN Display Missing Files===
192 missingFiles
="Missing files:";
193 #for key in "${!fileRollCall[@]}"; do echo "DEBUG :$key => ${fileRollCall[$key]}"; done
194 for key
in "${!fileRollCall[@]}"; do
195 value
="${fileRollCall[$key]}";
196 if [ "$value" = "false" ]; then
197 #echo "DEBUG :Missing files: $key => $value";
198 missingFiles
="$missingFiles""$key ";
202 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
203 echo "$missingFiles" 1>&2;
206 #===END Display Missing Files===
208 #===BEGIN Display Missing Directories===
209 missingDirs
="Missing dirs:";
210 #for key in "${!dirRollCall[@]}"; do echo "DEBUG :$key => ${dirRollCall[$key]}"; done
211 for key
in "${!dirRollCall[@]}"; do
212 value
="${dirRollCall[$key]}";
213 if [ "$value" = "false" ]; then
214 #echo "DEBUG :Missing dirs: $key => $value";
215 missingDirs
="$missingDirs""$key ";
219 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
220 echo "$missingDirs" 1>&2;
223 #===END Display Missing Directories===
225 #==END Display errors==
226 } # Display missing apps, files, dirs
229 # Desc: Appends [processed] file to tar
230 # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([process cmd])
232 # Input: arg1: path of file to be (processed and) written
233 # arg2: name to use for file inserted into tar
234 # arg3: tar archive path (must exist first)
235 # arg4: temporary working dir
236 # arg5: (optional) command string to process file (ex: "gpsbabel -i nmea -f - -o kml -F - ")
237 # Output: file written to disk
238 # Example: decrypt multiple large files in parallel
239 # appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
240 # appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
241 # appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
242 # Depends: bash 5.0.3, tar 1.30, cat 8.30, yell()
243 local fn fileName tarPath tmpDir
247 #yell "DEBUG :STATUS:$fn:Started appendFileTar()."
250 if ! [ -z "$2" ]; then fileName
="$2"; else yell
"ERROR:$fn:Not enough arguments."; exit 1; fi
251 # Check tar path is a file
252 if [ -f "$3" ]; then tarPath
="$3"; else yell
"ERROR:$fn:Tar archive arg not a file:$3"; exit 1; fi
254 if ! [ -z "$4" ]; then tmpDir
="$4"; else yell
"ERROR:$fn:No temporary working dir set."; exit 1; fi
255 # Set command strings
256 if ! [ -z "$5" ]; then cmd1
="$5"; else cmd1
="cat "; fi # command string
258 # Input command string
261 # Write to temporary working dir
262 eval "$cmd0 | $cmd1" > "$tmpDir"/"$fileName";
265 try
tar --append --directory="$tmpDir" --file="$tarPath" "$fileName";
266 #yell "DEBUG :STATUS:$fn:Finished appendFileTar()."
267 } # Append [processed] file to Tar archive
269 # Desc: Checks if string is an age-compatible pubkey
270 # Usage: checkAgePubkey [str pubkey]
272 # Input: arg1: string
273 # Output: return code 0: string is age-compatible pubkey
274 # return code 1: string is NOT an age-compatible pubkey
275 # age stderr (ex: there is stderr if invalid string provided)
276 # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 )
280 if echo "test" | age
-a -r "$argPubkey" 1>/dev
/null
; then
287 # Desc: Checks that a valid tar archive exists, creates one otherwise
288 # Usage: checkMakeTar [ path ]
290 # Input: arg1: path of tar archive
291 # Output: exit code 0 : tar readable
292 # exit code 1 : tar missing; created
293 # exit code 2 : tar not readable; moved; replaced
294 # Depends: bash 5, date 8, tar 1, try()
295 local pathTar returnFlag0 returnFlag1 returnFlag2
298 # Check if file is a valid tar archive
299 if tar --list --file="$pathTar" 1>/dev
/null
2>&1; then
300 ## T1: return success
301 returnFlag0
="tar valid";
303 ## F1: Check if file exists
304 if [[ -f "$pathTar" ]]; then
306 try
mv "$pathTar" "$pathTar""--broken--""$(date +%Y%m%dT%H%M%S)" && \
307 returnFlag1
="tar moved";
312 ## F2: Create tar archive, return 0
313 try
tar --create --file="$pathTar" --files-from=/dev
/null
&& \
314 returnFlag2
="tar created";
317 # Determine function return code
318 if [[ "$returnFlag0" = "tar valid" ]]; then
320 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
321 return 1; # tar missing so created
322 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
323 return 2; # tar not readable so moved; replaced
325 } # checks if arg1 is tar; creates one otherwise
327 # Desc: Date without separators (YYYYmmdd)
328 # Usage: dateShort ([str date])
330 # Input: arg1: 'date'-parsable timestamp string (optional)
331 # Output: stdout: date (ISO-8601, no separators)
332 # Depends: bash 5.0.3, date 8.30, yell()
333 local argTime timeCurrent timeInput dateCurrentShort
337 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
338 # Decide to parse current or supplied date
339 ## Check if time argument empty
340 if [[ -z "$argTime" ]]; then
341 ## T: Time argument empty, use current time
342 timeInput
="$timeCurrent";
344 ## F: Time argument exists, validate time
345 if date --date="$argTime" 1>/dev
/null
2>&1; then
346 ### T: Time argument is valid; use it
347 timeInput
="$argTime";
349 ### F: Time argument not valid; exit
350 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
353 # Construct and deliver separator-les date string
354 dateCurrentShort
="$(date -d "$timeInput" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
355 echo "$dateCurrentShort";
358 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
359 # Usage: dateTimeShort ([str date])
361 # Input: arg1: 'date'-parsable timestamp string (optional)
362 # Output: stdout: timestamp (ISO-8601, no separators)
364 local argTime timeCurrent timeInput timeCurrentShort
368 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
369 # Decide to parse current or supplied date
370 ## Check if time argument empty
371 if [[ -z "$argTime" ]]; then
372 ## T: Time argument empty, use current time
373 timeInput
="$timeCurrent";
375 ## F: Time argument exists, validate time
376 if date --date="$argTime" 1>/dev
/null
2>&1; then
377 ### T: Time argument is valid; use it
378 timeInput
="$argTime";
380 ### F: Time argument not valid; exit
381 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
384 # Construct and deliver separator-les date string
385 timeCurrentShort
="$(date -d "$timeInput" +%Y%m%dT%H%M%S%z)";
386 echo "$timeCurrentShort";
387 } # Get YYYYmmddTHHMMSS±zzzz
389 # Desc: Set time zone environment variable TZ
390 # Usage: setTimeZoneEV arg1
392 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
393 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
395 # exit code 0 on success
396 # exit code 1 on incorrect number of arguments
397 # exit code 2 if unable to validate arg1
398 # Depends: yell(), printenv 8.30, bash 5.0.3
399 # Tested on: Debian 10
400 local tzDir returnState argTimeZone
403 if ! [[ $# -eq 1 ]]; then
404 yell
"ERROR:Invalid argument count.";
408 # Read TZDIR env var if available
409 if printenv TZDIR
1>/dev
/null
2>&1; then
410 tzDir
="$(printenv TZDIR)";
412 tzDir
="/usr/share/zoneinfo";
416 if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then
417 yell
"ERROR:Invalid time zone argument.";
420 # Export ARG1 as TZ environment variable
421 TZ
="$argTimeZone" && export TZ
&& returnState
="true";
424 # Determine function return code
425 if [ "$returnState" = "true" ]; then
428 } # Exports TZ environment variable
430 yell
"$scriptVersion"
431 } # Display script version.
433 # Desc: Given seconds, output ISO-8601 duration string
434 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
435 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
436 # Usage: timeDuration [1:seconds] ([2:precision])
438 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
439 # arg2: precision level (optional; default=2)
440 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
441 # exit code 0: success
442 # exit code 1: error_input
443 # exit code 2: error_unknown
444 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
445 # Depends: date 8, bash 5, yell,
446 local argSeconds argPrecision precision returnState remainder
447 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
448 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
449 local witherPrecision output
450 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
452 argSeconds
="$1"; # read arg1 (seconds)
453 argPrecision
="$2"; # read arg2 (precision)
454 precision
=2; # set default precision
456 # Check that between one and two arguments is supplied
457 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
458 yell
"ERROR:Invalid number of arguments:$# . Exiting.";
459 returnState
="error_input"; fi
461 # Check that argSeconds provided
462 if [[ $# -ge 1 ]]; then
463 ## Check that argSeconds is a positive integer
464 if [[ "$argSeconds" =~ ^
[[:digit
:]]+$
]]; then
467 yell
"ERROR:argSeconds not a digit.";
468 returnState
="error_input";
471 yell
"ERROR:No argument provided. Exiting.";
475 # Consider whether argPrecision was provided
476 if [[ $# -eq 2 ]]; then
477 # Check that argPrecision is a positive integer
478 if [[ "$argPrecision" =~ ^
[[:digit
:]]+$
]] && [[ "$argPrecision" -gt 0 ]]; then
479 precision
="$argPrecision";
481 yell
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
482 returnState
="error_input";
488 remainder
="$argSeconds" ; # seconds
489 ## Calculate full years Y, update remainder
490 fullYears
=$
(( remainder
/ (365*24*60*60) ));
491 remainder
=$
(( remainder
- (fullYears
*365*24*60*60) ));
492 ## Calculate full months M, update remainder
493 fullMonths
=$
(( remainder
/ (30*24*60*60) ));
494 remainder
=$
(( remainder
- (fullMonths
*30*24*60*60) ));
495 ## Calculate full days D, update remainder
496 fullDays
=$
(( remainder
/ (24*60*60) ));
497 remainder
=$
(( remainder
- (fullDays
*24*60*60) ));
498 ## Calculate full hours H, update remainder
499 fullHours
=$
(( remainder
/ (60*60) ));
500 remainder
=$
(( remainder
- (fullHours
*60*60) ));
501 ## Calculate full minutes M, update remainder
502 fullMinutes
=$
(( remainder
/ (60) ));
503 remainder
=$
(( remainder
- (fullMinutes
*60) ));
504 ## Calculate full seconds S, update remainder
505 fullSeconds
=$
(( remainder
/ (1) ));
506 remainder
=$
(( remainder
- (remainder
*1) ));
507 ## Check which fields filled
508 if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi
509 if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi
510 if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi
511 if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi
512 if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi
513 if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi
515 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
516 witherPrecision
="false"
519 if $hasYears && [[ $precision -gt 0 ]]; then
521 witherPrecision
="true";
523 displayYears
="false";
525 if $witherPrecision; then ((precision--
)); fi;
528 if $hasMonths && [[ $precision -gt 0 ]]; then
529 displayMonths
="true";
530 witherPrecision
="true";
532 displayMonths
="false";
534 if $witherPrecision && [[ $precision -gt 0 ]]; then
535 displayMonths
="true";
537 if $witherPrecision; then ((precision--
)); fi;
540 if $hasDays && [[ $precision -gt 0 ]]; then
542 witherPrecision
="true";
546 if $witherPrecision && [[ $precision -gt 0 ]]; then
549 if $witherPrecision; then ((precision--
)); fi;
552 if $hasHours && [[ $precision -gt 0 ]]; then
554 witherPrecision
="true";
556 displayHours
="false";
558 if $witherPrecision && [[ $precision -gt 0 ]]; then
561 if $witherPrecision; then ((precision--
)); fi;
564 if $hasMinutes && [[ $precision -gt 0 ]]; then
565 displayMinutes
="true";
566 witherPrecision
="true";
568 displayMinutes
="false";
570 if $witherPrecision && [[ $precision -gt 0 ]]; then
571 displayMinutes
="true";
573 if $witherPrecision; then ((precision--
)); fi;
577 if $hasSeconds && [[ $precision -gt 0 ]]; then
578 displaySeconds
="true";
579 witherPrecision
="true";
581 displaySeconds
="false";
583 if $witherPrecision && [[ $precision -gt 0 ]]; then
584 displaySeconds
="true";
586 if $witherPrecision; then ((precision--
)); fi;
588 ## Determine whether or not the "T" separator is needed to separate date and time elements
589 if ( $displayHours ||
$displayMinutes ||
$displaySeconds); then
590 displayDateTime
="true"; else displayDateTime
="false"; fi
592 ## Construct duration output string
594 if $displayYears; then
595 output
=$output$fullYears"Y"; fi
596 if $displayMonths; then
597 output
=$output$fullMonths"M"; fi
598 if $displayDays; then
599 output
=$output$fullDays"D"; fi
600 if $displayDateTime; then
601 output
=$output"T"; fi
602 if $displayHours; then
603 output
=$output$fullHours"H"; fi
604 if $displayMinutes; then
605 output
=$output$fullMinutes"M"; fi
606 if $displaySeconds; then
607 output
=$output$fullSeconds"S"; fi
609 ## Output duration string to stdout
610 echo "$output" && returnState
="true";
612 #===Determine function return code===
613 if [ "$returnState" = "true" ]; then
615 elif [ "$returnState" = "error_input" ]; then
619 yell
"ERROR:Unknown";
623 } # Get duration (ex: PT10M4S )
625 # Desc: Report seconds until next day.
627 # Output: stdout: integer seconds until next day
628 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
629 # Usage: timeUntilNextDay
630 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
631 # Depends: date 8, echo 8, yell, try
633 local returnState timeCurrent timeNextDay secondsUntilNextDay returnState
634 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
635 timeNextDay
="$(date -d "$timeCurrent next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
636 secondsUntilNextDay
="$(( $(date +%s -d "$timeNextDay") - $(date +%s -d "$timeCurrent") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
637 if [[ "$secondsUntilNextDay" -gt 0 ]]; then
639 elif [[ "$secondsUntilNextDay" -eq 0 ]]; then
640 returnState
="warning_zero";
641 yell
"WARNING:Reported time until next day exactly zero.";
642 elif [[ "$secondsUntilNextDay" -lt 0 ]]; then
643 returnState
="warning_negative";
644 yell
"WARNING:Reported time until next day is negative.";
647 try
echo "$secondsUntilNextDay"; # Report
649 # Determine function return code
650 if [[ "$returnState" = "true" ]]; then
652 elif [[ "$returnState" = "warning_zero" ]]; then
654 elif [[ "$returnState" = "warning_negative" ]]; then
657 } # Report seconds until next day
659 # Desc: Report seconds until next hour
661 # Output: stdout: integer seconds until next hour
662 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
663 # Usage: timeUntilNextHour
664 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
666 local returnState timeCurrent timeNextHour secondsUntilNextHour
667 timeCurrent
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
668 timeNextHour
="$(date -d "$timeCurrent next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
669 secondsUntilNextHour
="$(( $(date +%s -d "$timeNextHour") - $(date +%s -d "$timeCurrent") ))"; # Calculate seconds until next hour (res. 1 second).
670 if [[ "$secondsUntilNextHour" -gt 0 ]]; then
672 elif [[ "$secondsUntilNextHour" -eq 0 ]]; then
673 returnState
="warning_zero";
674 yell
"WARNING:Reported time until next hour exactly zero.";
675 elif [[ "$secondsUntilNextHour" -lt 0 ]]; then
676 returnState
="warning_negative";
677 yell
"WARNING:Reported time until next hour is negative.";
680 try
echo "$secondsUntilNextHour"; # Report
682 # Determine function return code
683 if [[ "$returnState" = "true" ]]; then
685 elif [[ "$returnState" = "warning_zero" ]]; then
687 elif [[ "$returnState" = "warning_negative" ]]; then
690 } # Report seconds until next hour
692 # Desc: Validates Input
693 # Usage: validateInput [str input] [str input type]
695 # Input: arg1: string to validate
696 # arg2: string specifying input type (ex:"ssh_pubkey")
697 # Output: return code 0: if input string matched specified string type
698 # Depends: bash 5, yell()
700 local fn argInput argType
708 if [[ $# -gt 2 ]]; then yell
"ERROR:$0:$fn:Too many arguments."; exit 1; fi;
711 if [[ -z "$argInput" ]]; then return 1; fi
715 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
716 if [[ "$argType" = "ssh_pubkey" ]]; then
717 if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\
]*[[:alnum
:]+/=]*$
]]; then
721 ### Check for age1[:bech32:]
722 if [[ "$argType" = "age_pubkey" ]]; then
723 if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$
]]; then
727 if [[ "$argType" = "integer" ]]; then
728 if [[ "$argInput" =~ ^
[[:digit
:]]*$
]]; then
731 ## time element (year, month, week, day, hour, minute, second)
732 if [[ "$argType" = "time_element" ]]; then
733 if [[ "$argInput" = "year" ]] || \
734 [[ "$argInput" = "month" ]] || \
735 [[ "$argInput" = "week" ]] || \
736 [[ "$argInput" = "day" ]] || \
737 [[ "$argInput" = "hour" ]] || \
738 [[ "$argInput" = "minute" ]] || \
739 [[ "$argInput" = "second" ]]; then
742 # Return error if no condition matched.
744 } # Validates strings
746 magicInitWorkingDir
() {
747 # Desc: Determine temporary working directory from defaults or user input
748 # Usage: magicInitWorkingDir
749 # Input: vars: optionTmpDir, argTempDirPriority, dirTmpDefault
750 # Input: vars: scriptTimeStart
751 # Output: vars: dir_tmp
752 # Depends: bash 5.0.3, processArguments(), vbm(), yell()
753 # Parse '-t' option (user-specified temporary working dir)
754 ## Set dir_tmp_parent to user-specified value if specified
755 local fn dir_tmp_parent
760 vbm
"STATUS:$fn:Starting magicInitWorkingDir() function.";
761 if [[ "$optionTmpDir" = "true" ]]; then
762 if [[ -d "$argTempDirPriority" ]]; then
763 dir_tmp_parent
="$argTempDirPriority";
765 yell
"WARNING:$fn:Specified temporary working directory not valid:$argTempDirPriority";
766 exit 1; # Exit since user requires a specific temp dir and it is not available.
769 ## Set dir_tmp_parent to default or fallback otherwise
770 if [[ -d "$dirTmpDefault" ]]; then
771 dir_tmp_parent
="$dirTmpDefault";
772 elif [[ -d /tmp
]]; then
773 yell
"WARNING:$fn:$dirTmpDefault not available. Falling back to /tmp .";
774 dir_tmp_parent
="/tmp";
776 yell
"ERROR:$fn:No valid working directory available. Exiting.";
780 ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart)
781 dir_tmp
="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm
"DEBUG :$fn:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main().
782 vbm
"STATUS:$fn:Finished magicInitWorkingDir() function.";
784 magicInitCheckTar
() {
785 # Desc: Initializes or checks output tar
786 # input: vars: dirOut, bufferTTL, cmd_encrypt_suffix, cmd_compress_suffix
787 # input: vars: scriptHostname
788 # output: vars: pathout_tar
789 # depends: Bash 5.0.3, vbm(), dateShort(), checkMakeTar(), magicWriteVersion()
790 local fn checkMakeTarES
795 vbm
"STATUS:$fn:Starting magicInitCheckTar() function.";
797 pathout_tar
="$dirOut"/"$(dateShort "$
(date --date="$bufferTTL seconds ago" --iso-8601=seconds
)")"..
"$scriptHostname""$label""$cmd_compress_suffix""$cmd_encrypt_suffix".
tar && \
798 vbm
"STATUS:$fn:Set pathout_tar to:$pathout_tar";
799 # Validate pathout_tar as tar.
800 checkMakeTar
"$pathout_tar"; checkMakeTarES
="$?";
801 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
802 vbm
"STATUS:$fn:exit status before magicWriteVersion:$checkMakeTarES"
803 if [[ "$checkMakeTarES" -eq 1 ]] ||
[[ "$checkMakeTarES" -eq 2 ]]; then magicWriteVersion
; fi
804 vbm
"STATUS:$fn:Finished magicInitCheckTar() function.";
805 } # Initialize tar, set pathout_tar
806 magicParseCompressionArg
() {
807 # Desc: Parses compression arguments specified by '-c' option
808 # Input: vars: optionCompress
809 # Output: cmd_compress, cmd_compress_suffix
810 # Depends: processArguments(), vbm(), checkapp(), gzip 1.9
816 vbm
"STATUS:$fn:Starting magicParseCompressionArg() function.";
817 if [[ "$optionCompress" = "true" ]]; then # Check if compression option active
818 if checkapp
gzip; then # Check if gzip available
819 cmd_compress
="gzip " && vbm
"STATUS:$fn:cmd_compress:$cmd_compress";
820 cmd_compress_suffix
=".gz" && vbm
"STATUS:$fn:cmd_compress_suffix:$cmd_compress_suffix";
822 yell
"ERROR:$fn:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
825 cmd_compress
="tee /dev/null " && vbm
"STATUS:$fn:cmd_compress:$cmd_compress";
826 cmd_compress_suffix
="" && vbm
"STATUS:$fn:cmd_compress_suffix:$cmd_compress_suffix";
827 vbm
"DEBUG :$fn:Compression not enabled.";
829 vbm
"STATUS:$fn:Starting magicParseCompressionArg() function.";
830 } # Form compression cmd string and filename suffix
831 magicParseCustomTTL
() {
832 # Desc: Set user-specified TTLs for buffer and script
833 # Usage: magicParseCustomTTL
834 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
835 # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE
836 # Input: vars: bufferTTL (integer), scriptTTL_TE (string)
837 # Output: bufferTTL (integer), scriptTTL_TE (string)
838 # Depends: Bash 5.0.3, yell(), vbm(), validateInput(), showUsage()
844 vbm
"STATUS:$fn:Starting magicParseCustomTTL() function.";
845 # React to '-b, --buffer-ttl' option
846 if [[ "$optionCustomBufferTTL" = "true" ]]; then
847 ## T: Check if argCustomBufferTTL is an integer
848 if validateInput
"$argCustomBufferTTL" "integer"; then
849 ### T: argCustomBufferTTL is an integer
850 bufferTTL
="$argCustomBufferTTL" && vbm
"STATUS:$fn:Custom bufferTTL from -b:$bufferTTL";
852 ### F: argcustomBufferTTL is not an integer
853 yell
"ERROR:$fn:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1;
855 ## F: do not change bufferTTL
858 # React to '-B, --script-ttl' option
859 if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then
860 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
861 if validateInput
"$argCustomScriptTTL_TE" "time_element"; then
862 ### T: argCustomScriptTTL is a time element
863 scriptTTL_TE
="$argCustomScriptTTL_TE" && vbm
"STATUS:$fn:Custom scriptTTL_TE from -B:$scriptTTL_TE";
865 ### F: argcustomScriptTTL is not a time element
866 yell
"ERROR:$fn:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1;
868 ## F: do not change scriptTTL_TE
870 vbm
"STATUS:$fn:Starting magicParseCustomTTL() function.";
871 } # Sets custom script or buffer TTL if specified
873 # Desc: Parses -l option to set label
874 # In : optionLabel, argLabel
876 # Depends: Bash 5.0.3, vbm(), yell()
882 vbm
"STATUS:$fn:Started magicParseLabel() function.";
883 # Do nothing if optionLabel not set to true.
884 if [[ ! "$optionLabel" = "true" ]]; then
885 vbm
"STATUS:$fn:optionlabel not set to 'true'. Returning early.";
888 # Set label if optionLabel is true
889 if [[ "$optionLabel" = "true" ]]; then
890 label
="_""$argLabel";
891 vbm
"STATUS:$fn:Set label:$label";
893 vbm
"STATUS:$fn:Finished magicParseLabel() function.";
894 } # Set label used in output file name
895 magicParseProcessStrings
() {
896 # Desc: Processes user-supplied process strings into process commands for appendFileTar().
897 # Usage: magicParseProcessStrings
898 # In : vars: optionProcString optionNoStoreRaw optionStoreRaw argRawFileExt
899 # arry: argProcStrings, argProcFileExts
900 # Out: arry: procStrings, procFileExts
901 # Depends Bash 5.0.3, yell(), vbm()
907 vbm
"STATUS:$fn:Starting magicParseProcessStrings() function.";
908 vbm
"STATUS:$fn:var:optionProcString:$optionProcString";
909 vbm
"STATUS:$fn:var:optionNoStoreRaw:$optionNoStoreRaw";
910 vbm
"STATUS:$fn:var:optionStoreRaw:$optionStoreRaw";
911 vbm
"STATUS:$fn:var:argRawFileExt:$argRawFileExt";
912 vbm
"STATUS:$fn:ary:argProcStrings:${argProcStrings[*]}";
913 vbm
"STATUS:$fn:ary:argProcFileExts:${argProcFileExts[*]}"
915 ## Validate argRawFileExt
916 if [[ "$argRawFileExt" =~ ^
[.
][[:alnum
:]]*$
]]; then
917 rawFileExt
="$argRawFileExt" && \
918 vbm
"DEBUG :$fn:Set rawFileExt to \"$argRawFileExt\"";
920 vbm
"DEBUG :$fn:Validation failure for $argRawFileExt . Not set to rawFileExt.";
923 # Add default stdin output file entries for procStrings, procFileExts
924 ## Check if user specified that no raw stdin be saved.
925 if [[ ! "$optionNoStoreRaw" = "true" ]]; then
926 ### T: --no-store-raw not set. Store raw. Append procStrings with cat.
927 vbm
"DEBUG :$fn:--no-store-raw not set. Storing raw.";
928 #### Append procStrings array
929 procStrings
+=("cat ") && \
930 vbm
"DEBUG :$fn:Appended \"cat \" to procStrings";
931 vbm
"DEBUG :$fn:procStrings array:${procStrings[*]}";
932 #### Check if --store-raw set.
933 if [[ "$optionStoreRaw" = "true" ]]; then
934 ##### T: --store-raw set. Append procFileExts with user-specified file ext
935 vbm
"DEBUG :$fn:--store-raw set.";
936 procFileExts
+=("$rawFileExt") && \
937 vbm
"DEBUG :$fn:Appended $rawFileExt to procFileExts";
938 vbm
"STATUS:$fn:procFileExts array:${procFileExts[*]}";
940 ##### F: --store-raw not set. Append procFileExts with default ".stdin" file ext
941 ###### Append procFileExts array
942 procFileExts
+=(".stdin") && \
943 vbm
"DEBUG :$fn:Appended \".stdin\" to procFileExts";
944 vbm
"STATUS:$fn:procFileExts array:${procFileExts[*]}";
947 ### F: --no-store-raw set. Do not store raw.
948 #### Do not append procStrings or procFileExts arrays.
949 vbm
"STATUS:$fn:--no-store-raw set. Not storing raw.";
950 vbm
"STATUS:$fn:procFileExts array:${procFileExts[*]}";
953 # Do nothing more if optionProcString not set to true.
954 if [[ ! "$optionProcString" = "true" ]]; then
955 vbm
"STATUS:$fn:optionProcString not set to 'true'. Returning early.";
957 # Validate input array indices
958 ## Make sure that argProcStrings and argProcFileExts have same index counts
959 if ! [[ "${#argProcStrings[@]}" -eq "${#argProcFileExts[@]}" ]]; then
960 yell
"ERROR:$fn:Mismatch in number of elements in arrays argProcStrings and argProcFileExts:${#argProcStrings[@]} DNE ${#argProcFileExts[@]}";
961 yell
"STATUS:$fn:argProcStrings:${argProcStrings[*]}"; yell
"STATUS:$fn:argProcFileExts:${argProcFileExts[*]}"; exit 1; fi;
962 ## Make sure that no array elements are blank
963 for element
in "${argProcStrings[@]}"; do
964 if [[ -z "$element" ]]; then yell
"ERROR:$fn:Empty process string specified. Exiting."; exit 1; fi; done
965 for element
in "${argProcFileExts[@]}"; do
966 if [[ -z "$element" ]]; then yell
"ERROR:$fn:Empty output file extension specified. Exiting."; exit 1; fi; done
967 ## Make sure that no process string starts with '-' (ex: if only one arg supplied after '-p' option)
968 for element
in "${argProcStrings[@]}"; do
969 if [[ "$element" =~ ^
[-][[:print
:]]*$
]] && [[ ! "$element" =~ ^
[[:print
:]]*$
]]; then
970 yell
"ERROR:$fn:Illegal character '-' at start of process string element:\"$element\"";
972 vbm
"STATUS:$fn:Quick check shows argProcStrings and argProcFileExts appear to have valid contents.";
973 vbm
"STATUS:$fn:argProcStrings:${argProcStrings[*]}"
974 vbm
"STATUS:$fn:argProcStrings:${argProcFileExts[*]}"
975 procStrings
+=("${argProcStrings[@]}"); # Export process command strings
976 procFileExts
+=("${argProcFileExts[@]}"); # Export process command strings
977 vbm
"STATUS:$fn:Finished magicParseProcessStrings() function.";
978 } # Validate and save process strings and file extensions to arrays procStrings, procFileExts
979 magicParseRecipients
() {
980 # Desc: Parses recipient arguments specified by '-r' or '-R' options
981 # Usage: magicParseRecipients
982 # In : vars: optionEncrypt, optionRecArg, optionRecDir
983 # arry: argRecPubKeys (-r), argRecDir (-R)
984 # Out: vars: cmd_encrypt, cmd_encrypt_suffix
985 # Depends: head 8.30, checkapp(), checkAgePubkey(), validateInput()
986 local fn recipients recipientDir recFileLine updateRecipients
987 local -a recPubKeysValid candRecPubKeysValid
991 vbm
"STATUS:$fn:Starting magicParseRecipients() function.";
993 # Catch illegal option combinations
994 ## Catch case if '-e' is set but neither '-r' nor '-R' is set
995 if [[ "$optionEncrypt" = "true" ]] && \
996 ! { [[ "$optionRecArg" = "true" ]] ||
[[ "$optionRecDir" = "true" ]]; }; then
997 yell
"ERROR:$fn:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
998 ## Catch case if '-r' or '-R' set but '-e' is not
999 if [[ ! "$optionEncrypt" = "true" ]] && \
1000 { [[ "$optionRecArg" = "true" ]] ||
[[ "$optionRecDir" = "true" ]]; }; then
1001 yell
"ERROR:$fn:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
1003 # Handle no encryption cases
1004 if [[ ! "$optionEncrypt" = "true" ]]; then
1005 cmd_encrypt
="cat " && vbm
"STATUS:$fn:cmd_encrypt:$cmd_encrypt";
1006 cmd_encrypt_suffix
="" && vbm
"STATUS:$fn:cmd_encrypt_suffix:$cmd_encrypt_suffix";
1007 vbm
"DEBUG :$fn:Encryption not enabled.";
1010 # Handle encryption cases
1011 ## Check age availability
1012 if ! checkapp age
; then yell
"ERROR:$fn:age not available. Exiting."; exit 1; fi
1013 ## Parse '-r' options: validate and append pubkeys from argRecPubKeys to recPubKeysValid
1014 if [[ "$optionRecArg" = "true" ]]; then
1015 for pubkey
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
1016 vbm
"DEBUG :$fn:Testing pubkey string:$pubkey";
1017 if checkAgePubkey
"$pubkey" && \
1018 ( validateInput
"$pubkey" "ssh_pubkey" || validateInput
"$pubkey" "age_pubkey"); then
1019 #### Add validated pubkey to recPubKeysValid array
1020 recPubKeysValid
+=("$pubkey") && \
1021 vbm
"DEBUG :$fn:recPubkeysValid:pubkey added:$pubkey";
1023 yell
"ERROR:$fn:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
1026 vbm
"STATUS:$fn:Finished processing argRecPubKeys array";
1027 vbm
"DEBUG :$fn:Array of validated pubkeys:${recPubKeysValid[*]}";
1029 ## Parse '-R' options: validate and append pubkeys in argRecDir to recPubKeysValid
1030 if [[ "$optionRecDir" = "true" ]]; then
1031 ### Check that argRecDir is a directory
1032 if [[ -d "$argRecDir" ]]; then
1033 recipientDir
="$argRecDir" && \
1034 vbm
"STATUS:$fn:Recipient watch directory detected:\"$recipientDir\"";
1035 #### Initialize variable indicating outcome of pubkey review
1036 unset updateRecipients
1037 #### Add existing recipients from '-r' option
1038 candRecPubKeysValid
=("${recPubKeysValid[@]}");
1039 #### Parse files in recipientDir
1040 for file in "$recipientDir"/*; do
1041 ##### Read first line of each file
1042 recFileLine
="$(head -n1 "$file")" && \
1043 vbm
"STATUS:$fn:Checking if pubkey:\"$recFileLine\"";
1044 ##### check if first line is a valid pubkey
1045 if checkAgePubkey
"$recFileLine" && \
1046 ( validateInput
"$recFileLine" "ssh_pubkey" || validateInput
"$recFileLine" "age_pubkey"); then
1047 ###### T: add candidate pubkey to candRecPubKeysValid
1048 candRecPubKeysValid
+=("$recFileLine") && \
1049 vbm
"STATUS:$fn:RecDir pubkey is valid pubkey:\"$recFileLine\"";
1051 ###### F: throw warning;
1052 yell
"ERROR:$fn:Invalid recipient file detected. Not modifying recipient list:$recFileLine";
1053 updateRecipients
="false";
1056 #### Write candRecPubKeysValid array to recPubKeysValid if no invalid key detected
1057 if ! [[ "$updateRecipients" = "false" ]]; then
1058 recPubKeysValid
=("${candRecPubKeysValid[@]}") && \
1059 vbm
"STATUS:$fn:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\"";
1064 ## Form age recipient string from recPubKeysValid
1065 for pubkey
in "${recPubKeysValid[@]}"; do
1066 recipients
="$recipients""-r '$pubkey' ";
1067 vbm
"STATUS:$fn:Added pubkey for forming age recipient string:""$pubkey";
1068 vbm
"DEBUG :$fn:recipients:""$recipients";
1071 ## Output cmd_encrypt, cmd_encrypt_suffix from recipients
1072 cmd_encrypt
="age ""$recipients " && vbm
"STATUS:$fn:cmd_encrypt:$cmd_encrypt";
1073 cmd_encrypt_suffix
=".age" && vbm
"STATUS:$fn:cmd_encrypt_suffix:$cmd_encrypt_suffix";
1075 vbm
"STATUS:$fn:Finished magicParseRecipients() function.";
1076 } # Sets cmd_encrypt, cmd_encrypt_suffix from -r, -R args
1077 magicSetScriptTTL
() {
1078 #Desc: Sets script_TTL seconds from provided time_element string argument
1079 #Usage: magicSetScriptTTL [str time_element]
1080 #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour")
1081 #Output: var: scriptTTL (integer seconds)
1082 #Depends: timeUntilNextHour, timeUntilNextDay
1083 local fn argTimeElement
1085 # Save function name
1086 fn
="${FUNCNAME[0]}";
1088 vbm
"STATUS:$fn:Starting magicSetScriptTTL() function.";
1089 argTimeElement
="$1";
1090 if [[ "$argTimeElement" = "day" ]]; then
1091 # Set script lifespan to end at start of next day
1092 if ! scriptTTL
="$(timeUntilNextDay)"; then # sets scriptTTL, then checks exit code
1093 if [[ "$scriptTTL" -eq 0 ]]; then
1094 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
1096 yell
"ERROR:$fn:timeUntilNextDay exit code $?"; exit 1;
1099 elif [[ "$argTimeElement" = "hour" ]]; then
1100 # Set script lifespan to end at start of next hour
1101 if ! scriptTTL
="$(timeUntilNextHour)"; then # sets scriptTTL, then checks exit code
1102 if [[ "$scriptTTL" -eq 0 ]]; then
1103 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
1105 yell
"ERROR:$fn:timeUntilNextHour exit code $?"; exit 1;
1109 yell
"ERROR:$fn:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
1111 vbm
"STATUS:$fn:Finished magicSetScriptTTL() function.";
1112 } # Set scriptTTL in seconds until next (day|hour).
1113 magicWriteVersion
() {
1114 # Desc: Appends time-stamped VERSION to pathout_tar
1115 # Usage: magicWriteVersion
1116 # Input: vars: pathout_tar, dir_tmp
1117 # Input: vars: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname
1118 # Input: array: recPubKeysValid
1119 # Output: appends tar (pathout_tar)
1120 # Depends: bash 5.0.3, dateTimeShort(), appendArgTar()
1121 local fn fileoutVersion contentVersion pubKeyIndex pubKeyIndex
1123 # Save function name
1124 fn
="${FUNCNAME[0]}";
1126 vbm
"STATUS:$fn:Starting magicWriteVersion() function.";
1127 # Set VERSION file name
1128 fileoutVersion
="$(dateTimeShort)..VERSION";
1130 # Gather VERSION data in contentVersion
1131 contentVersion
="scriptVersion=$scriptVersion";
1132 #contentVersion="$contentVersion""\\n";
1133 contentVersion
="$contentVersion""\\n""scriptName=$scriptName";
1134 contentVersion
="$contentVersion""\\n""scriptURL=$scriptURL";
1135 contentVersion
="$contentVersion""\\n""ageVersion=$ageVersion";
1136 contentVersion
="$contentVersion""\\n""ageURL=$ageURL";
1137 contentVersion
="$contentVersion""\\n""date=$(date --iso-8601=seconds)";
1138 contentVersion
="$contentVersion""\\n""hostname=$scriptHostname";
1139 ## Add list of recipient pubkeys
1140 for pubkey
in "${recPubKeysValid[@]}"; do
1142 contentVersion
="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1144 ## Process newline escapes
1145 contentVersion
="$(echo -e "$contentVersion")"
1147 # Write contentVersion as file fileoutVersion and write-append to pathout_tar
1148 appendArgTar
"$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp" && \
1149 vbm
"STATUS:$fn:Appended $fileoutVersion to $pathout_tar";
1150 vbm
"STATUS:$fn:Finished magicWriteVersion() function.";
1151 } # write version data to pathout_tar via appendArgTar()
1152 magicProcessWriteBuffer
() {
1153 # Desc: process and write buffer
1154 # In : vars: bufferTTL bufferTTL_STR scriptHostname label dir_tmp SECONDS
1156 # Out: file:(pathout_tar)
1157 # Depends: Bash 5.0.3, date 8.30, yell(), vbm(), dateTimeShort(),
1158 ### Note: These arrays should all have the same number of elements:
1159 ### pathouts, fileouts, procFileExts, procStrings
1161 local fn timeBufferStartLong timeBufferStart fileoutBasename
1162 local -a fileouts pathouts
1163 local writeCmd1 writeCmd2 writeCmd3 writeCmd4
1165 # Debug:Get function name
1166 fn
="${FUNCNAME[0]}";
1168 vbm
"STATUS:$fn:Started magicProcessWriteBuffer().";
1169 vbm
"DEBUG :$fn:buffer array element count:${#buffer[@]}";
1170 vbm
"DEBUG :$fn:buffer array first element:${buffer[0]}";
1171 vbm
"DEBUG :$fn:buffer array last element :${buffer[-1]}";
1173 # Determine file paths (time is start of buffer period)
1174 ## Calculate start time
1175 timeBufferStartLong
="$(date --date="$bufferTTL seconds ago
" --iso-8601=seconds)" && \
1176 vbm
"DEBUG :$fn:timeBufferStartLong:$timeBufferStartLong";
1177 timeBufferStart
="$(dateTimeShort "$timeBufferStartLong" )" && \
1178 vbm
"DEBUG :$fn:timeBufferStart:$timeBufferStart"; # Note start time YYYYmmddTHHMMSS+zzzz (no separators)
1179 ## Set common basename
1180 fileoutBasename
="$timeBufferStart""--""$bufferTTL_STR""..""$scriptHostname""$label" && \
1181 vbm
"STATUS:$fn:Set fileoutBasename to:$fileoutBasename";
1182 ## Determine output file name array
1183 ### in: fileOutBasename cmd_compress_suffix cmd_encrypt_suffix procFileExts
1184 for fileExt
in "${procFileExts[@]}"; do
1185 fileouts
+=("$fileoutBasename""$fileExt""$cmd_compress_suffix""$cmd_encrypt_suffix") && \
1186 vbm
"STATUS:$fn:Added $fileExt to fileouts:${fileouts[*]}";
1188 for fileName
in "${fileouts[@]}"; do
1189 pathouts
+=("$dir_tmp"/"$fileName") && \
1190 vbm
"STATUS:$fn:Added $fileName to pathouts:${pathouts[*]}";
1192 ## Update pathout_tar
1195 # Process and write buffers to dir_tmp
1196 ## Prepare command strings
1197 writeCmd1
="printf \"%s\\\\n\" \"\${buffer[@]}\""; # printf "%s\\n" "${buffer[@]}"
1198 #writeCmd2="" # NOTE: Specified by parsing array procStrings
1199 writeCmd3
="$cmd_compress";
1200 writeCmd4
="$cmd_encrypt";
1202 ## Process buffer and write to dir_tmp
1203 for index
in "${!pathouts[@]}"; do
1204 writeCmd2
="${procStrings[$index]}";
1205 writeCmdAll
="$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" && vbm
"STATUS:$fn:Assembled command:\"$writeCmdAll\"";
1206 eval "$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" > "${pathouts[$index]}" && vbm
"STATUS:$fn:Wrote command output to ${pathouts[$index]}";
1209 # Append dir_tmp files to pathout_tar
1210 wait; # Wait to avoid collision with older magicProcessWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
1211 for index
in "${!pathouts[@]}"; do
1212 appendFileTar
"${pathouts[$index]}" "${fileouts[$index]}" "$pathout_tar" "$dir_tmp" && \
1213 vbm
"STATUS:$fn:Appended ${pathouts[$index]} to $pathout_tar";
1216 # Remove secured chunks from dir_tmp
1217 for path
in "${pathouts[@]}"; do
1218 rm "$path" && vbm
"STATUS:$fn:Removed:$path";
1221 vbm
"STATUS:$fn:Finished magicProcessWriteBuffer().";
1222 } # Process and Write buffer
1226 cmd | bklog [ options ]
1230 Display help information.
1232 Display script version.
1234 Display debugging info.
1237 -r, --recipient [ string pubkey ]
1238 Specify recipient. May be age or ssh pubkey.
1239 May be specified multiple times for multiple pubkeys.
1240 See https://github.com/FiloSottile/age
1241 -o, --output [ path dir ]
1242 Specify output directory to save logs. This option is required
1244 -p, --process-string [ filter command ] [ output file extension]
1245 Specify how to create and name a processed version of the stdin.
1246 For example, if stdin is 'nmea' location data:
1248 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx"
1250 This option would cause the stdin to 'bklog' to be piped into
1251 the 'gpsbabel' command, interpreted as 'nmea' data, converted
1252 into 'gpx' format, and then appended to the output tar file
1253 as a file with a '.gpx' extension.
1254 This option may be specified multiple times in order to output
1255 results of multiple different processing methods.
1256 -l, --label [ string ]
1257 Specify a label to be included in all output file names.
1258 Ex: 'location' if stdin is location data.
1259 -w, --store-raw [ file extension ]
1260 Specify file extension of file within output tar that contains
1261 raw stdin data. The default behavior is to always save raw stdin
1262 data in a '.stdin' file. Example usage when 'bklog' receives
1263 'nmea' data from 'gpspipe -r':
1267 Stdin data is saved in a '.nmea' file within the output tar.
1269 Do not store raw stdin in output tar.
1271 Compress output with gzip (before encryption if enabled).
1273 Specify time zone. (ex: "America/New_York")
1274 -t, --temp-dir [path dir]
1275 Specify parent directory for temporary working directory.
1277 -R, --recipient-dir [path dir]
1278 Specify directory containing files whose first lines are
1279 to be interpreted as pubkey strings (see '-r' option). Only
1280 one directory may be specified.
1281 -b, --buffer-ttl [integer]
1282 Specify custom buffer period in seconds (default: 300 seconds)
1283 -B, --script-ttl [time element string]
1284 Specify custom script time-to-live in seconds (default: "day")
1285 Valid values: "day", "hour"
1287 EXAMPLE: (bash script lines)
1288 $ gpspipe -r | /bin/bash bklog -v -e -c -z "UTC" -t "/dev/shm" \
1289 -r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \
1290 -r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \
1291 -R ~/.config/bklog/recipients -w ".nmea" -b 300 -B "day" \
1292 -o ~/Sync/Logs -l "location" \
1293 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" \
1294 -p "gpsbabel -i nmea -f - -o kml -F - " ".kml"
1296 } # Display information on how to use this script.
1299 # Desc: Main function
1302 # Outputs: file (pathout_tar)
1306 # Debug:Get function name
1307 fn
="${FUNCNAME[0]}";
1309 vbm
"STATUS:$fn:Started function main().";
1311 processArguments
"$@";
1312 ## Determine working directory
1313 magicInitWorkingDir
; # Sets dir_tmp from argTempDirPriority
1314 ## Set output encryption and compression option strings
1315 ### React to "-e", "-r", and "-R" (encryption recipients) options
1316 magicParseRecipients
; # Update cmd_encrypt, cmd_encrypt_suffix
1317 ### React to "-c" ("compression") option
1318 magicParseCompressionArg
; # Updates cmd_compress[_suffix]
1319 ## React to "-b" and "-B" (custom buffer and script TTL) options
1320 magicParseCustomTTL
; # Sets custom scriptTTL_TE and/or bufferTTL if specified
1321 ## React to "-p" (user-supplied process command and file extension strings) options
1322 magicParseProcessStrings
; # Sets arrays: procStrings, procFileExts
1323 ## React to "-l" (output file label) option
1324 magicParseLabel
; # sets label (ex: "_location")
1326 # Perform secondary setup operations
1327 ## Set script lifespan (scriptTTL from scriptTTL_TE)
1328 magicSetScriptTTL
"$scriptTTL_TE";
1329 ## File name substring (ISO-8601 duration from bufferTTL)
1330 bufferTTL_STR
="$(timeDuration "$bufferTTL")" && vbm
"DEBUG :$fn:bufferTTL_STR:$bufferTTL_STR";
1331 ## Init temp working dir
1332 try mkdir
"$dir_tmp" && vbm
"DEBUG :$fn:Working dir created at dir_tmp:$dir_tmp";
1333 ## Initialize output tar (set pathout_tar)
1335 ## Append VERSION file to tar
1338 # Check vital apps, files, dirs
1339 if ! checkapp
tar && ! checkdir
"$dirOut" "dir_tmp"; then
1340 yell
"ERROR:$fn:Critical components missing.";
1341 displayMissing
; yell
"Exiting."; exit 1; fi
1343 # MAIN LOOP: Run until script TTL seconds pass
1345 while [[ $SECONDS -lt "scriptTTL" ]]; do
1346 vbm
"STATUS:$fn:Starting buffer round:$bufferRound";
1347 bufferTOD
="$((SECONDS + bufferTTL))"; # Set buffer round time-of-death
1348 # Consume stdin to fill buffer until buffer time-of-death (TOD) arrives
1349 while read -r -t "$bufferTTL" line
&& [[ $SECONDS -lt "$bufferTOD" ]]; do
1350 # Append line to buffer array
1353 # Create dir_tmp if missing
1354 if ! [[ -d "$dir_tmp" ]]; then
1355 yell
"ERROR:$fn:dir_tmp existence failure:$dir_tmp";
1356 try mkdir
"$dir_tmp" && vbm
"DEBUG :$fn:Working dir recreated dir_tmp:$dir_tmp"; fi
1357 # Update cmd_encrypt, cmd_encrypt_suffix
1358 magicParseRecipients
;
1359 # Export buffer to asynchronous processing.
1360 magicProcessWriteBuffer
&
1361 unset buffer
; # Clear buffer array for next bufferRound
1362 # Increment buffer round
1368 try
rm -r "$dir_tmp" && vbm
"STATUS:$fn:Removed dir_tmp:$dir_tmp";
1370 vbm
"STATUS:$fn:Finished function main().";
1373 #===END Declare local script functions===
1374 #==END Define script parameters==
1376 #==BEGIN Perform work and exit==
1377 main
"$@" # Run main function.
1379 #==END Perform work and exit==
1381 # Author: Steven Baltakatei Sandoval;