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.8"; # 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 recPubKeysValidStatic
# for storing '-r' recipient pubkeys
27 declare -a argProcStrings argProcFileExts
# for storing buffer processing strings (ex: "gpsbabel -i nmea -f - -o gpx -F - ")
28 declare -Ag appRollCall
# Associative array for storing app status
29 declare -Ag fileRollCall
# Associative array for storing file status
30 declare -Ag dirRollCall
# Associative array for storing dir status
31 declare -a procStrings procFileExts
# Arrays for storing processing commands and resulting output file extensions
34 optionVerbose
=""; optionEncrypt
=""; dirOut
=""; optionEncrypt
=""; dir_tmp
="";
35 cmd_compress
="";cmd_compress_suffix
=""; cmd_encrypt
=""; cmd_encrypt_suffix
="";
37 #===END Initialize variables===
39 #===BEGIN Declare local script functions===
40 yell
() { echo "$0: $*" >&2; } #o Yell, Die, Try Three-Fingered Claw technique
41 die
() { yell
"$*"; exit 111; } #o Ref/Attrib: https://stackoverflow.com/a/25515370
42 try
() { "$@" || die
"cannot $*"; } #o
44 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
46 -v |
--verbose) optionVerbose
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode.
47 -h |
--help) showUsage
; exit 1;; # Display usage.
48 --version) showVersion
; exit 1;; # Show version
49 -o |
--output) if [ -d "$2" ]; then dirOut
="$2"; vbm
"DEBUG:dirOut:$dirOut"; shift; fi ;; # Define output directory.
50 -e |
--encrypt) optionEncrypt
="true"; vbm
"DEBUG:Encrypted output mode enabled.";; # Enable encryption
51 -r |
--recipient) optionRecipients
="true"; argRecPubKeys
+=("$2"); vbm
"STATUS:pubkey added:""$2"; shift;; # Add recipients
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 -R |
--recipient-dir) optionRecipients
="true"; optionRecDir
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir
56 -b |
--buffer-ttl) optionCustomBufferTTL
="true" && argCustomBufferTTL
="$2"; shift;; # Set custom buffer period (default: 300 seconds)
57 -B |
--script-ttl) optionCustomScriptTTL_TE
="true" && argCustomScriptTTL_TE
="$2"; shift;; # Set custom script TTL (default: "day")
58 -p |
--process-string) optionProcString
="true" && argProcStrings
+=("$2") && argProcFileExts
+=("$3") && vbm
"STATUS:file extension \"$2\" for output of processing string added:\"$3\""; shift; shift;;
59 -l |
--label) optionLabel
="true" && argLabel
="$2"; vbm
"DEBUG:Custom label received:$argLabel"; shift;;
60 -w |
--store-raw) optionStoreRaw
="true" && argRawFileExt
="$2"; vbm
"DEBUG:Raw stdin file extension received:$argRawFileExt"; shift;;
61 -W |
--no-store-raw) optionNoStoreRaw
="true"; vbm
"DEBUG:Option selected to not store raw stdin data."; shift;;
62 *) yell
"ERROR: Unrecognized argument: $1"; yell
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
66 } # Argument Processing
68 # Description: Prints verbose message ("vbm") to stderr if optionVerbose is set to "true".
69 # Usage: vbm "DEBUG:verbose message here"
74 # Depends: bash 5.0.3, echo 8.30, date 8.30
76 if [ "$optionVerbose" = "true" ]; then
77 functionTime
=$
(date --iso-8601=ns
); # Save current time in nano seconds.
78 echo "[$functionTime] ""$*" 1>&2; # Display argument text.
82 return 0; # Function finished.
83 } # Displays message if optionVerbose true
85 # Desc: If arg is a command, save result in assoc array 'appRollCall'
86 # Usage: checkapp arg1 arg2 arg3 ...
88 # Input: global assoc. array 'appRollCall'
89 # Output: adds/updates key(value) to global assoc array 'appRollCall'
95 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
96 appRollCall
[$arg]="true";
97 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
99 appRollCall
[$arg]="false"; returnState
="false";
103 #===Determine function return code===
104 if [ "$returnState" = "true" ]; then
109 } # Check that app exists
111 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
112 # Usage: checkfile arg1 arg2 arg3 ...
114 # Input: global assoc. array 'fileRollCall'
115 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
116 # Output: returns 0 if app found, 1 otherwise
117 # Depends: bash 5.0.3
122 if [ -f "$arg" ]; then
123 fileRollCall
["$arg"]="true";
124 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi;
126 fileRollCall
["$arg"]="false"; returnState
="false";
130 #===Determine function return code===
131 if [ "$returnState" = "true" ]; then
136 } # Check that file exists
138 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
139 # Usage: checkdir arg1 arg2 arg3 ...
141 # Input: global assoc. array 'dirRollCall'
142 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
143 # Output: returns 0 if app found, 1 otherwise
144 # Depends: Bash 5.0.3
149 if [ -d "$arg" ]; then
150 dirRollCall
["$arg"]="true";
151 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
153 dirRollCall
["$arg"]="false"; returnState
="false";
157 #===Determine function return code===
158 if [ "$returnState" = "true" ]; then
163 } # Check that dir exists
165 # Desc: Displays missing apps, files, and dirs
166 # Usage: displayMissing
168 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
169 # Output: stderr: messages indicating missing apps, file, or dirs
170 # Depends: bash 5, checkAppFileDir()
171 local missingApps value appMissing missingFiles fileMissing
172 local missingDirs dirMissing
174 #==BEGIN Display errors==
175 #===BEGIN Display Missing Apps===
176 missingApps
="Missing apps :";
177 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
178 for key
in "${!appRollCall[@]}"; do
179 value
="${appRollCall[$key]}";
180 if [ "$value" = "false" ]; then
181 #echo "DEBUG:Missing apps: $key => $value";
182 missingApps
="$missingApps""$key ";
186 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
187 echo "$missingApps" 1>&2;
190 #===END Display Missing Apps===
192 #===BEGIN Display Missing Files===
193 missingFiles
="Missing files:";
194 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
195 for key
in "${!fileRollCall[@]}"; do
196 value
="${fileRollCall[$key]}";
197 if [ "$value" = "false" ]; then
198 #echo "DEBUG:Missing files: $key => $value";
199 missingFiles
="$missingFiles""$key ";
203 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
204 echo "$missingFiles" 1>&2;
207 #===END Display Missing Files===
209 #===BEGIN Display Missing Directories===
210 missingDirs
="Missing dirs:";
211 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
212 for key
in "${!dirRollCall[@]}"; do
213 value
="${dirRollCall[$key]}";
214 if [ "$value" = "false" ]; then
215 #echo "DEBUG:Missing dirs: $key => $value";
216 missingDirs
="$missingDirs""$key ";
220 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
221 echo "$missingDirs" 1>&2;
224 #===END Display Missing Directories===
226 #==END Display errors==
227 } # Display missing apps, files, dirs
230 # Desc: Appends [processed] file to tar
231 # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([process cmd])
233 # Input: arg1: path of file to be (processed and) written
234 # arg2: name to use for file inserted into tar
235 # arg3: tar archive path (must exist first)
236 # arg4: temporary working dir
237 # arg5: (optional) command string to process file (ex: "gpsbabel -i nmea -f - -o kml -F - ")
238 # Output: file written to disk
239 # Example: decrypt multiple large files in parallel
240 # appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
241 # appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
242 # appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
243 # Depends: bash 5.0.3, tar 1.30, cat 8.30, yell()
244 local fn fileName tarPath tmpDir
248 #yell "DEBUG:STATUS:$fn:Started appendFileTar()."
251 if ! [ -z "$2" ]; then fileName
="$2"; else yell
"ERROR:$fn:Not enough arguments."; exit 1; fi
252 # Check tar path is a file
253 if [ -f "$3" ]; then tarPath
="$3"; else yell
"ERROR:$fn:Tar archive arg not a file:$3"; exit 1; fi
255 if ! [ -z "$4" ]; then tmpDir
="$4"; else yell
"ERROR:$fn:No temporary working dir set."; exit 1; fi
256 # Set command strings
257 if ! [ -z "$5" ]; then cmd1
="$5"; else cmd1
="cat "; fi # command string
259 # Input command string
262 # Write to temporary working dir
263 eval "$cmd0 | $cmd1" > "$tmpDir"/"$fileName";
266 try
tar --append --directory="$tmpDir" --file="$tarPath" "$fileName";
267 #yell "DEBUG:STATUS:$fn:Finished appendFileTar()."
268 } # Append [processed] file to Tar archive
270 # Desc: Checks if string is an age-compatible pubkey
271 # Usage: checkAgePubkey [str pubkey]
273 # Input: arg1: string
274 # Output: return code 0: string is age-compatible pubkey
275 # return code 1: string is NOT an age-compatible pubkey
276 # age stderr (ex: there is stderr if invalid string provided)
277 # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 )
281 if echo "test" | age
-a -r "$argPubkey" 1>/dev
/null
; then
288 # Desc: Checks that a valid tar archive exists, creates one otherwise
289 # Usage: checkMakeTar [ path ]
291 # Input: arg1: path of tar archive
292 # Output: exit code 0 : tar readable
293 # exit code 1 : tar missing; created
294 # exit code 2 : tar not readable; moved; replaced
295 # Depends: bash 5, date 8, tar 1, try()
296 local pathTar returnFlag0 returnFlag1 returnFlag2
299 # Check if file is a valid tar archive
300 if tar --list --file="$pathTar" 1>/dev
/null
2>&1; then
301 ## T1: return success
302 returnFlag0
="tar valid";
304 ## F1: Check if file exists
305 if [[ -f "$pathTar" ]]; then
307 try
mv "$pathTar" "$pathTar""--broken--""$(date +%Y%m%dT%H%M%S)" && \
308 returnFlag1
="tar moved";
313 ## F2: Create tar archive, return 0
314 try
tar --create --file="$pathTar" --files-from=/dev
/null
&& \
315 returnFlag2
="tar created";
318 # Determine function return code
319 if [[ "$returnFlag0" = "tar valid" ]]; then
321 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
322 return 1; # tar missing so created
323 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
324 return 2; # tar not readable so moved; replaced
326 } # checks if arg1 is tar; creates one otherwise
328 # Desc: Date without separators (YYYYmmdd)
329 # Usage: dateShort ([str date])
331 # Input: arg1: 'date'-parsable timestamp string (optional)
332 # Output: stdout: date (ISO-8601, no separators)
333 # Depends: bash 5.0.3, date 8.30, yell()
334 local argTime timeCurrent timeInput dateCurrentShort
338 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
339 # Decide to parse current or supplied date
340 ## Check if time argument empty
341 if [[ -z "$argTime" ]]; then
342 ## T: Time argument empty, use current time
343 timeInput
="$timeCurrent";
345 ## F: Time argument exists, validate time
346 if date --date="$argTime" 1>/dev
/null
2>&1; then
347 ### T: Time argument is valid; use it
348 timeInput
="$argTime";
350 ### F: Time argument not valid; exit
351 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
354 # Construct and deliver separator-les date string
355 dateCurrentShort
="$(date -d "$timeInput" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
356 echo "$dateCurrentShort";
359 # Desc: Set time zone environment variable TZ
360 # Usage: setTimeZoneEV arg1
362 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
363 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
365 # exit code 0 on success
366 # exit code 1 on incorrect number of arguments
367 # exit code 2 if unable to validate arg1
368 # Depends: yell(), printenv 8.30, bash 5.0.3
369 # Tested on: Debian 10
370 local tzDir returnState argTimeZone
373 if ! [[ $# -eq 1 ]]; then
374 yell
"ERROR:Invalid argument count.";
378 # Read TZDIR env var if available
379 if printenv TZDIR
1>/dev
/null
2>&1; then
380 tzDir
="$(printenv TZDIR)";
382 tzDir
="/usr/share/zoneinfo";
386 if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then
387 yell
"ERROR:Invalid time zone argument.";
390 # Export ARG1 as TZ environment variable
391 TZ
="$argTimeZone" && export TZ
&& returnState
="true";
394 # Determine function return code
395 if [ "$returnState" = "true" ]; then
398 } # Exports TZ environment variable
402 cmd | bklog [ options ]
406 Display help information.
408 Display script version.
410 Display debugging info.
413 -r, --recipient [ string pubkey ]
414 Specify recipient. May be age or ssh pubkey.
415 May be specified multiple times for multiple pubkeys.
416 See https://github.com/FiloSottile/age
417 -o, --output [ path dir ]
418 Specify output directory to save logs. This option is required
420 -p, --process-string [ filter command ] [ output file extension]
421 Specify how to create and name a processed version of the stdin.
422 For example, if stdin is 'nmea' location data:
424 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx"
426 This option would cause the stdin to 'bklog' to be piped into
427 the 'gpsbabel' command, interpreted as 'nmea' data, converted
428 into 'gpx' format, and then appended to the output tar file
429 as a file with a '.gpx' extension.
430 This option may be specified multiple times in order to output
431 results of multiple different processing methods.
432 -l, --label [ string ]
433 Specify a label to be included in all output file names.
434 Ex: 'location' if stdin is location data.
435 -w, --store-raw [ file extension ]
436 Specify file extension of file within output tar that contains
437 raw stdin data. The default behavior is to always save raw stdin
438 data in a '.stdin' file. Example usage when 'bklog' receives
439 'nmea' data from 'gpspipe -r':
443 Stdin data is saved in a '.nmea' file within the output tar.
445 Do not store raw stdin in output tar.
447 Compress output with gzip (before encryption if enabled).
449 Specify time zone. (ex: "America/New_York")
450 -t, --temp-dir [path dir]
451 Specify parent directory for temporary working directory.
453 -R, --recipient-dir [path dir]
454 Specify directory containing files whose first lines are
455 to be interpreted as pubkey strings (see '-r' option).
456 -b, --buffer-ttl [integer]
457 Specify custom buffer period in seconds (default: 300 seconds)
458 -B, --script-ttl [time element string]
459 Specify custom script time-to-live in seconds (default: "day")
460 Valid values: "day", "hour"
462 EXAMPLE: (bash script lines)
463 $ gpspipe -r | /bin/bash bklog -v -e -c -z "UTC" -t "/dev/shm" \
464 -r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \
465 -r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \
466 -R ~/.config/bklog/recipients -w ".nmea" -b 300 -B "day" \
467 -o ~/Sync/Logs -l "location" \
468 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" \
469 -p "gpsbabel -i nmea -f - -o kml -F - " ".kml"
471 } # Display information on how to use this script.
473 yell
"$scriptVersion"
474 } # Display script version.
476 # Desc: Given seconds, output ISO-8601 duration string
477 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
478 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
479 # Usage: timeDuration [1:seconds] ([2:precision])
481 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
482 # arg2: precision level (optional; default=2)
483 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
484 # exit code 0: success
485 # exit code 1: error_input
486 # exit code 2: error_unknown
487 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
488 # Depends: date 8, bash 5, yell,
489 local argSeconds argPrecision precision returnState remainder
490 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
491 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
492 local witherPrecision output
493 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
495 argSeconds
="$1"; # read arg1 (seconds)
496 argPrecision
="$2"; # read arg2 (precision)
497 precision
=2; # set default precision
499 # Check that between one and two arguments is supplied
500 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
501 yell
"ERROR:Invalid number of arguments:$# . Exiting.";
502 returnState
="error_input"; fi
504 # Check that argSeconds provided
505 if [[ $# -ge 1 ]]; then
506 ## Check that argSeconds is a positive integer
507 if [[ "$argSeconds" =~ ^
[[:digit
:]]+$
]]; then
510 yell
"ERROR:argSeconds not a digit.";
511 returnState
="error_input";
514 yell
"ERROR:No argument provided. Exiting.";
518 # Consider whether argPrecision was provided
519 if [[ $# -eq 2 ]]; then
520 # Check that argPrecision is a positive integer
521 if [[ "$argPrecision" =~ ^
[[:digit
:]]+$
]] && [[ "$argPrecision" -gt 0 ]]; then
522 precision
="$argPrecision";
524 yell
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
525 returnState
="error_input";
531 remainder
="$argSeconds" ; # seconds
532 ## Calculate full years Y, update remainder
533 fullYears
=$
(( remainder
/ (365*24*60*60) ));
534 remainder
=$
(( remainder
- (fullYears
*365*24*60*60) ));
535 ## Calculate full months M, update remainder
536 fullMonths
=$
(( remainder
/ (30*24*60*60) ));
537 remainder
=$
(( remainder
- (fullMonths
*30*24*60*60) ));
538 ## Calculate full days D, update remainder
539 fullDays
=$
(( remainder
/ (24*60*60) ));
540 remainder
=$
(( remainder
- (fullDays
*24*60*60) ));
541 ## Calculate full hours H, update remainder
542 fullHours
=$
(( remainder
/ (60*60) ));
543 remainder
=$
(( remainder
- (fullHours
*60*60) ));
544 ## Calculate full minutes M, update remainder
545 fullMinutes
=$
(( remainder
/ (60) ));
546 remainder
=$
(( remainder
- (fullMinutes
*60) ));
547 ## Calculate full seconds S, update remainder
548 fullSeconds
=$
(( remainder
/ (1) ));
549 remainder
=$
(( remainder
- (remainder
*1) ));
550 ## Check which fields filled
551 if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi
552 if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi
553 if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi
554 if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi
555 if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi
556 if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi
558 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
559 witherPrecision
="false"
562 if $hasYears && [[ $precision -gt 0 ]]; then
564 witherPrecision
="true";
566 displayYears
="false";
568 if $witherPrecision; then ((precision--
)); fi;
571 if $hasMonths && [[ $precision -gt 0 ]]; then
572 displayMonths
="true";
573 witherPrecision
="true";
575 displayMonths
="false";
577 if $witherPrecision && [[ $precision -gt 0 ]]; then
578 displayMonths
="true";
580 if $witherPrecision; then ((precision--
)); fi;
583 if $hasDays && [[ $precision -gt 0 ]]; then
585 witherPrecision
="true";
589 if $witherPrecision && [[ $precision -gt 0 ]]; then
592 if $witherPrecision; then ((precision--
)); fi;
595 if $hasHours && [[ $precision -gt 0 ]]; then
597 witherPrecision
="true";
599 displayHours
="false";
601 if $witherPrecision && [[ $precision -gt 0 ]]; then
604 if $witherPrecision; then ((precision--
)); fi;
607 if $hasMinutes && [[ $precision -gt 0 ]]; then
608 displayMinutes
="true";
609 witherPrecision
="true";
611 displayMinutes
="false";
613 if $witherPrecision && [[ $precision -gt 0 ]]; then
614 displayMinutes
="true";
616 if $witherPrecision; then ((precision--
)); fi;
620 if $hasSeconds && [[ $precision -gt 0 ]]; then
621 displaySeconds
="true";
622 witherPrecision
="true";
624 displaySeconds
="false";
626 if $witherPrecision && [[ $precision -gt 0 ]]; then
627 displaySeconds
="true";
629 if $witherPrecision; then ((precision--
)); fi;
631 ## Determine whether or not the "T" separator is needed to separate date and time elements
632 if ( $displayHours ||
$displayMinutes ||
$displaySeconds); then
633 displayDateTime
="true"; else displayDateTime
="false"; fi
635 ## Construct duration output string
637 if $displayYears; then
638 output
=$output$fullYears"Y"; fi
639 if $displayMonths; then
640 output
=$output$fullMonths"M"; fi
641 if $displayDays; then
642 output
=$output$fullDays"D"; fi
643 if $displayDateTime; then
644 output
=$output"T"; fi
645 if $displayHours; then
646 output
=$output$fullHours"H"; fi
647 if $displayMinutes; then
648 output
=$output$fullMinutes"M"; fi
649 if $displaySeconds; then
650 output
=$output$fullSeconds"S"; fi
652 ## Output duration string to stdout
653 echo "$output" && returnState
="true";
655 #===Determine function return code===
656 if [ "$returnState" = "true" ]; then
658 elif [ "$returnState" = "error_input" ]; then
662 yell
"ERROR:Unknown";
666 } # Get duration (ex: PT10M4S )
668 # Desc: Report seconds until next day.
670 # Output: stdout: integer seconds until next day
671 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
672 # Usage: timeUntilNextDay
673 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
674 # Depends: date 8, echo 8, yell, try
676 local returnState timeCurrent timeNextDay secondsUntilNextDay returnState
677 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
678 timeNextDay
="$(date -d "$timeCurrent next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
679 secondsUntilNextDay
="$(( $(date +%s -d "$timeNextDay") - $(date +%s -d "$timeCurrent") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
680 if [[ "$secondsUntilNextDay" -gt 0 ]]; then
682 elif [[ "$secondsUntilNextDay" -eq 0 ]]; then
683 returnState
="warning_zero";
684 yell
"WARNING:Reported time until next day exactly zero.";
685 elif [[ "$secondsUntilNextDay" -lt 0 ]]; then
686 returnState
="warning_negative";
687 yell
"WARNING:Reported time until next day is negative.";
690 try
echo "$secondsUntilNextDay"; # Report
692 # Determine function return code
693 if [[ "$returnState" = "true" ]]; then
695 elif [[ "$returnState" = "warning_zero" ]]; then
697 elif [[ "$returnState" = "warning_negative" ]]; then
700 } # Report seconds until next day
702 # Desc: Report seconds until next hour
704 # Output: stdout: integer seconds until next hour
705 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
706 # Usage: timeUntilNextHour
707 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
709 local returnState timeCurrent timeNextHour secondsUntilNextHour
710 timeCurrent
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
711 timeNextHour
="$(date -d "$timeCurrent next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
712 secondsUntilNextHour
="$(( $(date +%s -d "$timeNextHour") - $(date +%s -d "$timeCurrent") ))"; # Calculate seconds until next hour (res. 1 second).
713 if [[ "$secondsUntilNextHour" -gt 0 ]]; then
715 elif [[ "$secondsUntilNextHour" -eq 0 ]]; then
716 returnState
="warning_zero";
717 yell
"WARNING:Reported time until next hour exactly zero.";
718 elif [[ "$secondsUntilNextHour" -lt 0 ]]; then
719 returnState
="warning_negative";
720 yell
"WARNING:Reported time until next hour is negative.";
723 try
echo "$secondsUntilNextHour"; # Report
725 # Determine function return code
726 if [[ "$returnState" = "true" ]]; then
728 elif [[ "$returnState" = "warning_zero" ]]; then
730 elif [[ "$returnState" = "warning_negative" ]]; then
733 } # Report seconds until next hour
735 # Desc: Validates Input
736 # Usage: validateInput [str input] [str input type]
738 # Input: arg1: string to validate
739 # arg2: string specifying input type (ex:"ssh_pubkey")
740 # Output: return code 0: if input string matched specified string type
741 # Depends: bash 5, yell()
743 local fn argInput argType
751 if [[ $# -gt 2 ]]; then yell
"ERROR:$0:$fn:Too many arguments."; exit 1; fi;
754 if [[ -z "$argInput" ]]; then return 1; fi
758 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
759 if [[ "$argType" = "ssh_pubkey" ]]; then
760 if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\
]*[[:alnum
:]+/=]*$
]]; then
764 ### Check for age1[:bech32:]
765 if [[ "$argType" = "age_pubkey" ]]; then
766 if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$
]]; then
770 if [[ "$argType" = "integer" ]]; then
771 if [[ "$argInput" =~ ^
[[:digit
:]]*$
]]; then
774 ## time element (year, month, week, day, hour, minute, second)
775 if [[ "$argType" = "time_element" ]]; then
776 if [[ "$argInput" = "year" ]] || \
777 [[ "$argInput" = "month" ]] || \
778 [[ "$argInput" = "week" ]] || \
779 [[ "$argInput" = "day" ]] || \
780 [[ "$argInput" = "hour" ]] || \
781 [[ "$argInput" = "minute" ]] || \
782 [[ "$argInput" = "second" ]]; then
785 # Return error if no condition matched.
787 } # Validates strings
789 magicInitWorkingDir
() {
790 # Desc: Determine temporary working directory from defaults or user input
791 # Usage: magicInitWorkingDir
792 # Input: vars: optionTmpDir, argTempDirPriority, dirTmpDefault
793 # Input: vars: scriptTimeStart
794 # Output: vars: dir_tmp
795 # Depends: bash 5.0.3, processArguments(), vbm(), yell()
796 # Parse '-t' option (user-specified temporary working dir)
797 ## Set dir_tmp_parent to user-specified value if specified
800 if [[ "$optionTmpDir" = "true" ]]; then
801 if [[ -d "$argTempDirPriority" ]]; then
802 dir_tmp_parent
="$argTempDirPriority";
804 yell
"WARNING:Specified temporary working directory not valid:$argTempDirPriority";
805 exit 1; # Exit since user requires a specific temp dir and it is not available.
808 ## Set dir_tmp_parent to default or fallback otherwise
809 if [[ -d "$dirTmpDefault" ]]; then
810 dir_tmp_parent
="$dirTmpDefault";
811 elif [[ -d /tmp
]]; then
812 yell
"WARNING:$dirTmpDefault not available. Falling back to /tmp .";
813 dir_tmp_parent
="/tmp";
815 yell
"ERROR:No valid working directory available. Exiting.";
819 ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart)
820 dir_tmp
="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm
"DEBUG:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main().
822 magicInitCheckTar
() {
823 # Desc: Initializes or checks output tar
824 # input: vars: dirOut, bufferTTL, cmd_encrypt_suffix, cmd_compress_suffix
825 # input: vars: scriptHostname
826 # output: vars: pathout_tar
827 # depends: Bash 5.0.3, vbm(), dateShort(), checkMakeTar(), magicWriteVersion()
830 pathout_tar
="$dirOut"/"$(dateShort "$
(date --date="$bufferTTL seconds ago" --iso-8601=seconds
)")"..
"$scriptHostname""$label""$cmd_compress_suffix""$cmd_encrypt_suffix".
tar && \
831 vbm
"STATUS:Set pathout_tar to:$pathout_tar";
832 # Validate pathout_tar as tar.
833 checkMakeTar
"$pathout_tar";
834 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
835 vbm
"exit status before magicWriteVersion:$?"
836 if [[ $?
-eq 1 ]] ||
[[ $?
-eq 2 ]]; then magicWriteVersion
; fi
837 } # Initialize tar, set pathout_tar
838 magicParseCompressionArg
() {
839 # Desc: Parses compression arguments specified by '-c' option
840 # Input: vars: optionCompress
841 # Output: cmd_compress, cmd_compress_suffix
842 # Depends: processArguments(), vbm(), checkapp(), gzip 1.9
843 if [[ "$optionCompress" = "true" ]]; then # Check if compression option active
844 if checkapp
gzip; then # Check if gzip available
845 cmd_compress
="gzip " && vbm
"cmd_compress:$cmd_compress";
846 cmd_compress_suffix
=".gz" && vbm
"cmd_compress_suffix:$cmd_compress_suffix";
848 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
851 cmd_compress
="tee /dev/null " && vbm
"cmd_compress:$cmd_compress";
852 cmd_compress_suffix
="" && vbm
"cmd_compress_suffix:$cmd_compress_suffix";
853 vbm
"DEBUG:Compression not enabled.";
855 } # Form compression cmd string and filename suffix
856 magicParseCustomTTL
() {
857 # Desc: Set user-specified TTLs for buffer and script
858 # Usage: magicParseCustomTTL
859 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
860 # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE
861 # Input: vars: bufferTTL (integer), scriptTTL_TE (string)
862 # Output: bufferTTL (integer), scriptTTL_TE (string)
863 # Depends: Bash 5.0.3, yell(), vbm(), validateInput(), showUsage()
865 # React to '-b, --buffer-ttl' option
866 if [[ "$optionCustomBufferTTL" = "true" ]]; then
867 ## T: Check if argCustomBufferTTL is an integer
868 if validateInput
"$argCustomBufferTTL" "integer"; then
869 ### T: argCustomBufferTTL is an integer
870 bufferTTL
="$argCustomBufferTTL" && vbm
"Custom bufferTTL from -b:$bufferTTL";
872 ### F: argcustomBufferTTL is not an integer
873 yell
"ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1;
875 ## F: do not change bufferTTL
878 # React to '-B, --script-ttl' option
879 if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then
880 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
881 if validateInput
"$argCustomScriptTTL_TE" "time_element"; then
882 ### T: argCustomScriptTTL is a time element
883 scriptTTL_TE
="$argCustomScriptTTL_TE" && vbm
"Custom scriptTTL_TE from -B:$scriptTTL_TE";
885 ### F: argcustomScriptTTL is not a time element
886 yell
"ERROR:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1;
888 ## F: do not change scriptTTL_TE
890 } # Sets custom script or buffer TTL if specified
892 # Desc: Parses -l option to set label
893 # In : optionLabel, argLabel
895 # Depends: Bash 5.0.3, vbm(), yell()
897 vbm
"STATUS:Started magicParseLabel() function.";
898 # Do nothing if optionLabel not set to true.
899 if [[ ! "$optionLabel" = "true" ]]; then
900 vbm
"STATUS:optionlabel not set to 'true'. Returning early.";
903 # Set label if optionLabel is true
904 if [[ "$optionLabel" = "true" ]]; then
905 label
="_""$argLabel";
906 vbm
"STATUS:Set label:$label";
908 vbm
"STATUS:Finished magicParseLabel() function.";
909 } # Set label used in output file name
910 magicParseProcessStrings
() {
911 # Desc: Processes user-supplied process strings into process commands for appendFileTar().
912 # Usage: magicParseProcessStrings
913 # In : vars: optionProcString optionNoStoreRaw optionStoreRaw argRawFileExt
914 # arry: argProcStrings, argProcFileExts
915 # Out: arry: procStrings, procFileExts
916 # Depends Bash 5.0.3, yell(), vbm()
919 vbm
"STATUS:Starting magicParseProcessStrings() function.";
920 vbm
"var:optionProcString:$optionProcString";
921 vbm
"var:optionNoStoreRaw:$optionNoStoreRaw";
922 vbm
"var:optionStoreRaw:$optionStoreRaw";
923 vbm
"var:argRawFileExt:$argRawFileExt";
924 vbm
"ary:argProcStrings:${argProcStrings[*]}";
925 vbm
"ary:argProcFileExts:${argProcFileExts[*]}"
927 ## Validate argRawFileExt
928 if [[ "$argRawFileExt" =~ ^
[.
][[:alnum
:]]*$
]]; then
929 rawFileExt
="$argRawFileExt";
932 # Add default stdin output file entries for procStrings, procFileExts
933 ## Check if user specified that no raw stdin be saved.
934 if [[ ! "$optionNoStoreRaw" = "true" ]]; then
935 ### T: --no-store-raw not set. Store raw. Append procStrings with cat.
936 #### Append procStrings array
937 procStrings
+=("cat ");
938 #### Check if --store-raw set.
939 if [[ "$optionStoreRaw" = "true" ]]; then
940 ##### T: --store-raw set. Append procFileExts with user-specified file ext
941 procFileExts
+=("$rawFileExt");
943 ##### F: --store-raw not set. Append procFileExts with default ".stdin" file ext
944 ###### Append procFileExts array
945 procFileExts
+=(".stdin");
948 ### F: --no-store-raw set. Do not store raw.
949 #### Do not append procStrings or procFileExts arrays.
953 # Do nothing more if optionProcString not set to true.
954 if [[ ! "$optionProcString" = "true" ]]; then
955 vbm
"STATUS: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:Mismatch in number of elements in arrays argProcStrings and argProcFileExts:${#argProcStrings[@]} DNE ${#argProcFileExts[@]}";
961 yell
"argProcStrings:${argProcStrings[*]}"; yell
"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:Empty process string specified. Exiting."; exit 1; fi; done
965 for element
in "${argProcFileExts[@]}"; do
966 if [[ -z "$element" ]]; then yell
"ERROR: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:Illegal character '-' at start of process string element:\"$element\"";
972 vbm
"STATUS:Quick check shows argProcStrings and argProcFileExts appear to have valid contents.";
973 procStrings
=("${argProcStrings[@]}"); # Export process command strings
974 procFileExts
=("${argProcFileExts[@]}"); # Export process command strings
975 vbm
"STATUS:Finished magicParseProcessStrings() function.";
976 } # Validate and save process strings and file extensions to arrays procStrings, procFileExts
977 magicParseRecipientArgs
() {
978 # Desc: Parses recipient arguments specified by '-r' option
979 # Input: vars: optionEncrypt, optionRecipients
980 # arry: argRecPubKeys from processArguments()
981 # Output: vars: cmd_encrypt, cmd_encrypt_suffix
982 # arry: recPubKeysValid, recPubKeysValidStatic
983 # Depends: processArguments(), yell(), vbm(), checkapp(), checkAgePubkey(), validateInput()
986 # Check if encryption option active.
987 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
988 if checkapp age
; then # Check that age is available.
989 for pubkey
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
990 vbm
"DEBUG:Testing pubkey string:$pubkey";
991 if checkAgePubkey
"$pubkey" && \
992 ( validateInput
"$pubkey" "ssh_pubkey" || validateInput
"$pubkey" "age_pubkey"); then
993 #### Form age recipient string
994 recipients
="$recipients""-r '$pubkey' ";
995 vbm
"STATUS:Added pubkey for forming age recipient string:""$pubkey";
996 vbm
"DEBUG:recipients:""$recipients";
997 #### Add validated pubkey to recPubKeysValid array
998 recPubKeysValid
+=("$pubkey") && vbm
"DEBUG:recPubkeysValid:pubkey added:$pubkey";
1000 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
1003 vbm
"DEBUG:Finished processing argRecPubKeys array";
1004 vbm
"STATUS:Array of validated pubkeys:${recPubKeysValid[*]}";
1005 recPubKeysValidStatic
=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function
1007 ## Form age command string
1008 cmd_encrypt
="age ""$recipients " && vbm
"cmd_encrypt:$cmd_encrypt";
1009 cmd_encrypt_suffix
=".age" && vbm
"cmd_encrypt_suffix:$cmd_encrypt_suffix";
1011 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
1014 cmd_encrypt
="tee /dev/null " && vbm
"cmd_encrypt:$cmd_encrypt";
1015 cmd_encrypt_suffix
="" && vbm
"cmd_encrypt_suffix:$cmd_encrypt_suffix";
1016 vbm
"DEBUG:Encryption not enabled."
1018 # Catch case if '-e' is set but '-r' or '-R' is not
1019 if [[ "$optionEncrypt" = "true" ]] && [[ ! "$optionRecipients" = "true" ]]; then
1020 yell
"ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
1021 # Catch case if '-r' or '-R' set but '-e' is not
1022 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
1023 yell
"ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
1024 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
1025 magicParseRecipientDir
() {
1026 # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory")
1027 # Inputs: vars: optionEncrypt, optionRecDir, argRecDir,
1028 # arry: recPubKeysValid
1029 # Outputs: arry: recPubKeysValid
1030 # Depends: processArguments(), yell(), vbm(), validateInput(), checkAgePubkey()
1031 local recipientDir recFileLine updateRecipients
1032 declare -a candRecPubKeysValid
1034 # Check that '-e' and '-R' set
1035 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
1036 ### Check that argRecDir is a directory.
1037 if [[ -d "$argRecDir" ]]; then
1038 recipientDir
="$argRecDir" && vbm
"STATUS:Recipient watch directory detected:\"$recipientDir\"";
1039 #### Initialize variable indicating outcome of pubkey review
1040 unset updateRecipients
1041 #### Add existing recipients
1042 candRecPubKeysValid
=("${recPubKeysValidStatic[@]}");
1043 #### Parse files in recipientDir
1044 for file in "$recipientDir"/*; do
1045 ##### Read first line of each file
1046 recFileLine
="$(head -n1 "$file")" && vbm
"STATUS:Checking if pubkey:\"$recFileLine\"";
1047 ##### check if first line is a valid pubkey
1048 if checkAgePubkey
"$recFileLine" && \
1049 ( validateInput
"$recFileLine" "ssh_pubkey" || validateInput
"$recFileLine" "age_pubkey"); then
1050 ###### T: add candidate pubkey to candRecPubKeysValid
1051 candRecPubKeysValid
+=("$recFileLine") && vbm
"STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\"";
1053 ###### F: throw warning;
1054 yell
"ERROR:Invalid recipient file detected. Not modifying recipient list."
1055 updateRecipients
="false";
1058 #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected
1059 if ! [[ "$updateRecipients" = "false" ]]; then
1060 recPubKeysValid
=("${candRecPubKeysValid[@]}") && vbm
"STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\"";
1063 yell
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
1066 # Handle case if '-R' set but '-e' not set
1067 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
1068 yell
"ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi;
1069 } # Update recPubKeysValid with argRecDir
1070 magicSetScriptTTL
() {
1071 #Desc: Sets script_TTL seconds from provided time_element string argument
1072 #Usage: magicSetScriptTTL [str time_element]
1073 #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour")
1074 #Output: var: scriptTTL (integer seconds)
1075 #Depends: timeUntilNextHour, timeUntilNextDay
1076 local argTimeElement
1078 argTimeElement
="$1";
1079 if [[ "$argTimeElement" = "day" ]]; then
1080 # Set script lifespan to end at start of next day
1081 if ! scriptTTL
="$(timeUntilNextDay)"; then # sets scriptTTL, then checks exit code
1082 if [[ "$scriptTTL" -eq 0 ]]; then
1083 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
1085 yell
"ERROR: timeUntilNextDay exit code $?"; exit 1;
1088 elif [[ "$argTimeElement" = "hour" ]]; then
1089 # Set script lifespan to end at start of next hour
1090 if ! scriptTTL
="$(timeUntilNextHour)"; then # sets scriptTTL, then checks exit code
1091 if [[ "$scriptTTL" -eq 0 ]]; then
1092 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
1094 yell
"ERROR: timeUntilNextHour exit code $?"; exit 1;
1098 yell
"ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
1100 } # Set scriptTTL in seconds until next (day|hour).
1101 magicWriteVersion
() {
1102 # Desc: Appends time-stamped VERSION to pathout_tar
1103 # Usage: magicWriteVersion
1104 # Input: vars: pathout_tar, dir_tmp
1105 # Input: vars: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname
1106 # Input: array: recPubKeysValid
1107 # Output: appends tar (pathout_tar)
1108 # Depends: bash 5.0.3, dateTimeShort(), appendArgTar()
1109 local fileoutVersion contentVersion pubKeyIndex pubKeyIndex
1111 # Set VERSION file name
1112 fileoutVersion
="$(dateTimeShort)..VERSION";
1114 # Gather VERSION data in contentVersion
1115 contentVersion
="scriptVersion=$scriptVersion";
1116 #contentVersion="$contentVersion""\\n";
1117 contentVersion
="$contentVersion""\\n""scriptName=$scriptName";
1118 contentVersion
="$contentVersion""\\n""scriptURL=$scriptURL";
1119 contentVersion
="$contentVersion""\\n""ageVersion=$ageVersion";
1120 contentVersion
="$contentVersion""\\n""ageURL=$ageURL";
1121 contentVersion
="$contentVersion""\\n""date=$(date --iso-8601=seconds)";
1122 contentVersion
="$contentVersion""\\n""hostname=$scriptHostname";
1123 ## Add list of recipient pubkeys
1124 for pubkey
in "${recPubKeysValid[@]}"; do
1126 contentVersion
="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1128 ## Process newline escapes
1129 contentVersion
="$(echo -e "$contentVersion")"
1131 # Write contentVersion as file fileoutVersion and write-append to pathout_tar
1132 appendArgTar
"$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp";
1133 } # write version data to pathout_tar via appendArgTar()
1134 magicProcessWriteBuffer
() {
1135 # Desc: process and write buffer
1136 # In : vars: bufferTTL bufferTTL_STR scriptHostname label dir_tmp SECONDS
1138 # Out: file:(pathout_tar)
1139 # Depends: Bash 5.0.3, date 8.30, yell(), vbm(), dateTimeShort(),
1140 ### Note: These arrays should all have the same number of elements:
1141 ### pathouts, fileouts, procFileExts, procStrings
1143 local fn timeBufferStartLong timeBufferStart fileoutBasename
1144 local -a fileouts pathouts
1145 local writeCmd1 writeCmd2 writeCmd3 writeCmd4
1147 vbm
"DEBUG:STATUS:$fn:Started magicProcessWriteBuffer().";
1148 # Debug:Get function name
1149 fn
="${FUNCNAME[0]}";
1151 # Determine file paths (time is start of buffer period)
1152 ## Calculate start time
1153 timeBufferStartLong
="$(date --date="$bufferTTL seconds ago
" --iso-8601=seconds)" && \
1154 vbm
"timeBufferStartLong:$timeBufferStartLong";
1155 timeBufferStart
="$(dateTimeShort "$timeBufferStartLong" )" && \
1156 vbm
"timeBufferStart:$timeBufferStart"; # Note start time YYYYmmddTHHMMSS+zzzz (no separators)
1157 ## Set common basename
1158 fileoutBasename
="$timeBufferStart""--""$bufferTTL_STR""..""$scriptHostname""$label" && \
1159 vbm
"STATUS:Set fileoutBasename to:$fileoutBasename";
1160 ## Determine output file name array
1161 ### in: fileOutBasename cmd_compress_suffix cmd_encrypt_suffix procFileExts
1162 for fileExt
in "${procFileExts[@]}"; do
1163 fileouts
+=("$fileoutBasename""$fileExt""$cmd_compress_suffix""$cmd_encrypt_suffix") && \
1164 vbm
"STATUS:Added $fileExt to fileouts:${fileouts[*]}";
1166 for fileName
in "${fileouts[@]}"; do
1167 pathouts
+=("$dir_tmp"/"$fileName") && \
1168 vbm
"STATUS:Added $fileName to pathouts:${pathouts[*]}";
1170 ## Update pathout_tar
1173 # Process and write buffers to dir_tmp
1174 ## Prepare command strings
1175 writeCmd1
="printf \"%s\\\\n\" \"\${buffer[@]}\""; # printf "%s\\n" "${buffer[@]}"
1176 #writeCmd2="" # NOTE: Specified by parsing array procStrings
1177 writeCmd3
="$cmd_compress";
1178 writeCmd4
="$cmd_encrypt";
1180 ## Process buffer and write to dir_tmp
1181 for index
in "${!pathouts[@]}"; do
1182 writeCmd2
="${procStrings[$index]}"
1183 eval "$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" >> "${pathouts[$index]}";
1186 # Append dir_tmp files to pathout_tar
1187 wait; # Wait to avoid collision with older magicProcessWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
1188 for index
in "${!pathouts[@]}"; do
1189 appendFileTar
"${pathouts[$index]}" "${fileouts[$index]}" "$pathout_tar" "$dir_tmp";
1192 # Remove secured chunks from dir_tmp
1193 for path
in "${pathouts[@]}"; do
1197 vbm
"DEBUG:STATUS:$fn:Finished magicProcessWriteBuffer().";
1198 } # Process and Write buffer
1202 processArguments
"$@";
1203 ## Determine working directory
1204 magicInitWorkingDir
; # Sets dir_tmp from argTempDirPriority
1205 ## Set output encryption and compression option strings
1206 ### React to "-e" and "-r" ("encryption recipients") options
1207 magicParseRecipientArgs
; # Updates recPubKeysValid, cmd_encrypt[_suffix] from argRecPubKeys
1208 ### React to "-R" ("recipient directory") option
1209 magicParseRecipientDir
; # Updates recPubKeysValid
1210 ### React to "-c" ("compression") option
1211 magicParseCompressionArg
; # Updates cmd_compress[_suffix]
1212 ## React to "-b" and "-B" (custom buffer and script TTL) options
1213 magicParseCustomTTL
; # Sets custom scriptTTL_TE and/or bufferTTL if specified
1214 ## React to "-p" (user-supplied process command and file extension strings) options
1215 magicParseProcessStrings
; # Sets arrays: procStrings, procFileExts
1216 ## React to "-l" (output file label) option
1217 magicParseLabel
; # sets label (ex: "_location")
1219 # Perform secondary setup operations
1220 ## Set script lifespan (scriptTTL from scriptTTL_TE)
1221 magicSetScriptTTL
"$scriptTTL_TE";
1222 ## File name substring (ISO-8601 duration from bufferTTL)
1223 bufferTTL_STR
="$(timeDuration "$bufferTTL")" && vbm
"DEBUG:bufferTTL_STR:$bufferTTL_STR";
1224 ## Init temp working dir
1225 try mkdir
"$dir_tmp" && vbm
"DEBUG:Working dir created at dir_tmp:$dir_tmp";
1226 ## Initialize output tar (set pathout_tar)
1229 # Check vital apps, files, dirs
1230 if ! checkapp
tar && ! checkdir
"$dirOut" "dir_tmp"; then
1231 yell
"ERROR:Critical components missing.";
1232 displayMissing
; yell
"Exiting."; exit 1; fi
1234 # MAIN LOOP: Run until script TTL seconds pass
1236 while [[ $SECONDS -lt "scriptTTL" ]]; do
1237 bufferTOD
="$((SECONDS + bufferTTL))"; # Set buffer round time-of-death
1238 lineCount
=0; # Debug counter
1239 # Consume stdin to fill buffer until buffer time-of-death (TOD) arrives
1240 while read -r -t "$bufferTTL" line
&& [[ $SECONDS -lt "$bufferTOD" ]]; do
1241 # Append line to buffer array
1243 vbm
"DEBUG:Processing line:$lineCount";
1244 vbm
"DEBUG:Current line :$line";
1245 vbm
"DEBUG:buf elem count :${#buffer[@]}";
1248 # Create dir_tmp if missing
1249 if ! [[ -d "$dir_tmp" ]]; then yell
"ERROR:dir_tmp existence failure:$dir_tmp"; try mkdir
"$dir_tmp" && vbm
"DEBUG:Working dir recreated dir_tmp:$dir_tmp"; fi
1250 # Update encryption recipient array
1251 magicParseRecipientDir
; # Update recPubKeysValid with argRecDir
1252 # Export buffer to asynchronous processing.
1253 magicProcessWriteBuffer
&
1254 unset buffer
; # Clear buffer array for next bufferRound
1255 # Increment buffer round
1261 try
rm -r "$dir_tmp" && vbm
"Removed dir_tmp:$dir_tmp";
1263 vbm
"STATUS:Main function finished.";
1266 #===END Declare local script functions===
1267 #==END Define script parameters==
1269 #==BEGIN Perform work and exit==
1270 main
"$@" # Run main function.
1272 #==END Perform work and exit==
1274 # Author: Steven Baltakatei Sandoval;