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.11"; # 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: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
360 # Usage: dateTimeShort ([str date])
362 # Input: arg1: 'date'-parsable timestamp string (optional)
363 # Output: stdout: timestamp (ISO-8601, no separators)
365 local argTime timeCurrent timeInput timeCurrentShort
369 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
370 # Decide to parse current or supplied date
371 ## Check if time argument empty
372 if [[ -z "$argTime" ]]; then
373 ## T: Time argument empty, use current time
374 timeInput
="$timeCurrent";
376 ## F: Time argument exists, validate time
377 if date --date="$argTime" 1>/dev
/null
2>&1; then
378 ### T: Time argument is valid; use it
379 timeInput
="$argTime";
381 ### F: Time argument not valid; exit
382 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
385 # Construct and deliver separator-les date string
386 timeCurrentShort
="$(date -d "$timeInput" +%Y%m%dT%H%M%S%z)";
387 echo "$timeCurrentShort";
388 } # Get YYYYmmddTHHMMSS±zzzz
390 # Desc: Set time zone environment variable TZ
391 # Usage: setTimeZoneEV arg1
393 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
394 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
396 # exit code 0 on success
397 # exit code 1 on incorrect number of arguments
398 # exit code 2 if unable to validate arg1
399 # Depends: yell(), printenv 8.30, bash 5.0.3
400 # Tested on: Debian 10
401 local tzDir returnState argTimeZone
404 if ! [[ $# -eq 1 ]]; then
405 yell
"ERROR:Invalid argument count.";
409 # Read TZDIR env var if available
410 if printenv TZDIR
1>/dev
/null
2>&1; then
411 tzDir
="$(printenv TZDIR)";
413 tzDir
="/usr/share/zoneinfo";
417 if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then
418 yell
"ERROR:Invalid time zone argument.";
421 # Export ARG1 as TZ environment variable
422 TZ
="$argTimeZone" && export TZ
&& returnState
="true";
425 # Determine function return code
426 if [ "$returnState" = "true" ]; then
429 } # Exports TZ environment variable
433 cmd | bklog [ options ]
437 Display help information.
439 Display script version.
441 Display debugging info.
444 -r, --recipient [ string pubkey ]
445 Specify recipient. May be age or ssh pubkey.
446 May be specified multiple times for multiple pubkeys.
447 See https://github.com/FiloSottile/age
448 -o, --output [ path dir ]
449 Specify output directory to save logs. This option is required
451 -p, --process-string [ filter command ] [ output file extension]
452 Specify how to create and name a processed version of the stdin.
453 For example, if stdin is 'nmea' location data:
455 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx"
457 This option would cause the stdin to 'bklog' to be piped into
458 the 'gpsbabel' command, interpreted as 'nmea' data, converted
459 into 'gpx' format, and then appended to the output tar file
460 as a file with a '.gpx' extension.
461 This option may be specified multiple times in order to output
462 results of multiple different processing methods.
463 -l, --label [ string ]
464 Specify a label to be included in all output file names.
465 Ex: 'location' if stdin is location data.
466 -w, --store-raw [ file extension ]
467 Specify file extension of file within output tar that contains
468 raw stdin data. The default behavior is to always save raw stdin
469 data in a '.stdin' file. Example usage when 'bklog' receives
470 'nmea' data from 'gpspipe -r':
474 Stdin data is saved in a '.nmea' file within the output tar.
476 Do not store raw stdin in output tar.
478 Compress output with gzip (before encryption if enabled).
480 Specify time zone. (ex: "America/New_York")
481 -t, --temp-dir [path dir]
482 Specify parent directory for temporary working directory.
484 -R, --recipient-dir [path dir]
485 Specify directory containing files whose first lines are
486 to be interpreted as pubkey strings (see '-r' option).
487 -b, --buffer-ttl [integer]
488 Specify custom buffer period in seconds (default: 300 seconds)
489 -B, --script-ttl [time element string]
490 Specify custom script time-to-live in seconds (default: "day")
491 Valid values: "day", "hour"
493 EXAMPLE: (bash script lines)
494 $ gpspipe -r | /bin/bash bklog -v -e -c -z "UTC" -t "/dev/shm" \
495 -r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \
496 -r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \
497 -R ~/.config/bklog/recipients -w ".nmea" -b 300 -B "day" \
498 -o ~/Sync/Logs -l "location" \
499 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" \
500 -p "gpsbabel -i nmea -f - -o kml -F - " ".kml"
502 } # Display information on how to use this script.
504 yell
"$scriptVersion"
505 } # Display script version.
507 # Desc: Given seconds, output ISO-8601 duration string
508 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
509 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
510 # Usage: timeDuration [1:seconds] ([2:precision])
512 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
513 # arg2: precision level (optional; default=2)
514 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
515 # exit code 0: success
516 # exit code 1: error_input
517 # exit code 2: error_unknown
518 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
519 # Depends: date 8, bash 5, yell,
520 local argSeconds argPrecision precision returnState remainder
521 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
522 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
523 local witherPrecision output
524 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
526 argSeconds
="$1"; # read arg1 (seconds)
527 argPrecision
="$2"; # read arg2 (precision)
528 precision
=2; # set default precision
530 # Check that between one and two arguments is supplied
531 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
532 yell
"ERROR:Invalid number of arguments:$# . Exiting.";
533 returnState
="error_input"; fi
535 # Check that argSeconds provided
536 if [[ $# -ge 1 ]]; then
537 ## Check that argSeconds is a positive integer
538 if [[ "$argSeconds" =~ ^
[[:digit
:]]+$
]]; then
541 yell
"ERROR:argSeconds not a digit.";
542 returnState
="error_input";
545 yell
"ERROR:No argument provided. Exiting.";
549 # Consider whether argPrecision was provided
550 if [[ $# -eq 2 ]]; then
551 # Check that argPrecision is a positive integer
552 if [[ "$argPrecision" =~ ^
[[:digit
:]]+$
]] && [[ "$argPrecision" -gt 0 ]]; then
553 precision
="$argPrecision";
555 yell
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
556 returnState
="error_input";
562 remainder
="$argSeconds" ; # seconds
563 ## Calculate full years Y, update remainder
564 fullYears
=$
(( remainder
/ (365*24*60*60) ));
565 remainder
=$
(( remainder
- (fullYears
*365*24*60*60) ));
566 ## Calculate full months M, update remainder
567 fullMonths
=$
(( remainder
/ (30*24*60*60) ));
568 remainder
=$
(( remainder
- (fullMonths
*30*24*60*60) ));
569 ## Calculate full days D, update remainder
570 fullDays
=$
(( remainder
/ (24*60*60) ));
571 remainder
=$
(( remainder
- (fullDays
*24*60*60) ));
572 ## Calculate full hours H, update remainder
573 fullHours
=$
(( remainder
/ (60*60) ));
574 remainder
=$
(( remainder
- (fullHours
*60*60) ));
575 ## Calculate full minutes M, update remainder
576 fullMinutes
=$
(( remainder
/ (60) ));
577 remainder
=$
(( remainder
- (fullMinutes
*60) ));
578 ## Calculate full seconds S, update remainder
579 fullSeconds
=$
(( remainder
/ (1) ));
580 remainder
=$
(( remainder
- (remainder
*1) ));
581 ## Check which fields filled
582 if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi
583 if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi
584 if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi
585 if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi
586 if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi
587 if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi
589 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
590 witherPrecision
="false"
593 if $hasYears && [[ $precision -gt 0 ]]; then
595 witherPrecision
="true";
597 displayYears
="false";
599 if $witherPrecision; then ((precision--
)); fi;
602 if $hasMonths && [[ $precision -gt 0 ]]; then
603 displayMonths
="true";
604 witherPrecision
="true";
606 displayMonths
="false";
608 if $witherPrecision && [[ $precision -gt 0 ]]; then
609 displayMonths
="true";
611 if $witherPrecision; then ((precision--
)); fi;
614 if $hasDays && [[ $precision -gt 0 ]]; then
616 witherPrecision
="true";
620 if $witherPrecision && [[ $precision -gt 0 ]]; then
623 if $witherPrecision; then ((precision--
)); fi;
626 if $hasHours && [[ $precision -gt 0 ]]; then
628 witherPrecision
="true";
630 displayHours
="false";
632 if $witherPrecision && [[ $precision -gt 0 ]]; then
635 if $witherPrecision; then ((precision--
)); fi;
638 if $hasMinutes && [[ $precision -gt 0 ]]; then
639 displayMinutes
="true";
640 witherPrecision
="true";
642 displayMinutes
="false";
644 if $witherPrecision && [[ $precision -gt 0 ]]; then
645 displayMinutes
="true";
647 if $witherPrecision; then ((precision--
)); fi;
651 if $hasSeconds && [[ $precision -gt 0 ]]; then
652 displaySeconds
="true";
653 witherPrecision
="true";
655 displaySeconds
="false";
657 if $witherPrecision && [[ $precision -gt 0 ]]; then
658 displaySeconds
="true";
660 if $witherPrecision; then ((precision--
)); fi;
662 ## Determine whether or not the "T" separator is needed to separate date and time elements
663 if ( $displayHours ||
$displayMinutes ||
$displaySeconds); then
664 displayDateTime
="true"; else displayDateTime
="false"; fi
666 ## Construct duration output string
668 if $displayYears; then
669 output
=$output$fullYears"Y"; fi
670 if $displayMonths; then
671 output
=$output$fullMonths"M"; fi
672 if $displayDays; then
673 output
=$output$fullDays"D"; fi
674 if $displayDateTime; then
675 output
=$output"T"; fi
676 if $displayHours; then
677 output
=$output$fullHours"H"; fi
678 if $displayMinutes; then
679 output
=$output$fullMinutes"M"; fi
680 if $displaySeconds; then
681 output
=$output$fullSeconds"S"; fi
683 ## Output duration string to stdout
684 echo "$output" && returnState
="true";
686 #===Determine function return code===
687 if [ "$returnState" = "true" ]; then
689 elif [ "$returnState" = "error_input" ]; then
693 yell
"ERROR:Unknown";
697 } # Get duration (ex: PT10M4S )
699 # Desc: Report seconds until next day.
701 # Output: stdout: integer seconds until next day
702 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
703 # Usage: timeUntilNextDay
704 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
705 # Depends: date 8, echo 8, yell, try
707 local returnState timeCurrent timeNextDay secondsUntilNextDay returnState
708 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
709 timeNextDay
="$(date -d "$timeCurrent next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
710 secondsUntilNextDay
="$(( $(date +%s -d "$timeNextDay") - $(date +%s -d "$timeCurrent") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
711 if [[ "$secondsUntilNextDay" -gt 0 ]]; then
713 elif [[ "$secondsUntilNextDay" -eq 0 ]]; then
714 returnState
="warning_zero";
715 yell
"WARNING:Reported time until next day exactly zero.";
716 elif [[ "$secondsUntilNextDay" -lt 0 ]]; then
717 returnState
="warning_negative";
718 yell
"WARNING:Reported time until next day is negative.";
721 try
echo "$secondsUntilNextDay"; # Report
723 # Determine function return code
724 if [[ "$returnState" = "true" ]]; then
726 elif [[ "$returnState" = "warning_zero" ]]; then
728 elif [[ "$returnState" = "warning_negative" ]]; then
731 } # Report seconds until next day
733 # Desc: Report seconds until next hour
735 # Output: stdout: integer seconds until next hour
736 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
737 # Usage: timeUntilNextHour
738 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
740 local returnState timeCurrent timeNextHour secondsUntilNextHour
741 timeCurrent
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
742 timeNextHour
="$(date -d "$timeCurrent next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
743 secondsUntilNextHour
="$(( $(date +%s -d "$timeNextHour") - $(date +%s -d "$timeCurrent") ))"; # Calculate seconds until next hour (res. 1 second).
744 if [[ "$secondsUntilNextHour" -gt 0 ]]; then
746 elif [[ "$secondsUntilNextHour" -eq 0 ]]; then
747 returnState
="warning_zero";
748 yell
"WARNING:Reported time until next hour exactly zero.";
749 elif [[ "$secondsUntilNextHour" -lt 0 ]]; then
750 returnState
="warning_negative";
751 yell
"WARNING:Reported time until next hour is negative.";
754 try
echo "$secondsUntilNextHour"; # Report
756 # Determine function return code
757 if [[ "$returnState" = "true" ]]; then
759 elif [[ "$returnState" = "warning_zero" ]]; then
761 elif [[ "$returnState" = "warning_negative" ]]; then
764 } # Report seconds until next hour
766 # Desc: Validates Input
767 # Usage: validateInput [str input] [str input type]
769 # Input: arg1: string to validate
770 # arg2: string specifying input type (ex:"ssh_pubkey")
771 # Output: return code 0: if input string matched specified string type
772 # Depends: bash 5, yell()
774 local fn argInput argType
782 if [[ $# -gt 2 ]]; then yell
"ERROR:$0:$fn:Too many arguments."; exit 1; fi;
785 if [[ -z "$argInput" ]]; then return 1; fi
789 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
790 if [[ "$argType" = "ssh_pubkey" ]]; then
791 if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\
]*[[:alnum
:]+/=]*$
]]; then
795 ### Check for age1[:bech32:]
796 if [[ "$argType" = "age_pubkey" ]]; then
797 if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$
]]; then
801 if [[ "$argType" = "integer" ]]; then
802 if [[ "$argInput" =~ ^
[[:digit
:]]*$
]]; then
805 ## time element (year, month, week, day, hour, minute, second)
806 if [[ "$argType" = "time_element" ]]; then
807 if [[ "$argInput" = "year" ]] || \
808 [[ "$argInput" = "month" ]] || \
809 [[ "$argInput" = "week" ]] || \
810 [[ "$argInput" = "day" ]] || \
811 [[ "$argInput" = "hour" ]] || \
812 [[ "$argInput" = "minute" ]] || \
813 [[ "$argInput" = "second" ]]; then
816 # Return error if no condition matched.
818 } # Validates strings
820 magicInitWorkingDir
() {
821 # Desc: Determine temporary working directory from defaults or user input
822 # Usage: magicInitWorkingDir
823 # Input: vars: optionTmpDir, argTempDirPriority, dirTmpDefault
824 # Input: vars: scriptTimeStart
825 # Output: vars: dir_tmp
826 # Depends: bash 5.0.3, processArguments(), vbm(), yell()
827 # Parse '-t' option (user-specified temporary working dir)
828 ## Set dir_tmp_parent to user-specified value if specified
831 vbm
"Starting magicInitWorkingDir() function.";
832 if [[ "$optionTmpDir" = "true" ]]; then
833 if [[ -d "$argTempDirPriority" ]]; then
834 dir_tmp_parent
="$argTempDirPriority";
836 yell
"WARNING:Specified temporary working directory not valid:$argTempDirPriority";
837 exit 1; # Exit since user requires a specific temp dir and it is not available.
840 ## Set dir_tmp_parent to default or fallback otherwise
841 if [[ -d "$dirTmpDefault" ]]; then
842 dir_tmp_parent
="$dirTmpDefault";
843 elif [[ -d /tmp
]]; then
844 yell
"WARNING:$dirTmpDefault not available. Falling back to /tmp .";
845 dir_tmp_parent
="/tmp";
847 yell
"ERROR:No valid working directory available. Exiting.";
851 ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart)
852 dir_tmp
="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm
"DEBUG:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main().
853 vbm
"Finished magicInitWorkingDir() function.";
855 magicInitCheckTar
() {
856 # Desc: Initializes or checks output tar
857 # input: vars: dirOut, bufferTTL, cmd_encrypt_suffix, cmd_compress_suffix
858 # input: vars: scriptHostname
859 # output: vars: pathout_tar
860 # depends: Bash 5.0.3, vbm(), dateShort(), checkMakeTar(), magicWriteVersion()
862 vbm
"Starting magicInitCheckTar() function.";
864 pathout_tar
="$dirOut"/"$(dateShort "$
(date --date="$bufferTTL seconds ago" --iso-8601=seconds
)")"..
"$scriptHostname""$label""$cmd_compress_suffix""$cmd_encrypt_suffix".
tar && \
865 vbm
"STATUS:Set pathout_tar to:$pathout_tar";
866 # Validate pathout_tar as tar.
867 checkMakeTar
"$pathout_tar";
868 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
869 vbm
"exit status before magicWriteVersion:$?"
870 if [[ $?
-eq 1 ]] ||
[[ $?
-eq 2 ]]; then magicWriteVersion
; fi
871 vbm
"Finished magicInitCheckTar() function.";
872 } # Initialize tar, set pathout_tar
873 magicParseCompressionArg
() {
874 # Desc: Parses compression arguments specified by '-c' option
875 # Input: vars: optionCompress
876 # Output: cmd_compress, cmd_compress_suffix
877 # Depends: processArguments(), vbm(), checkapp(), gzip 1.9
879 vbm
"Starting magicParseCompressionArg() function.";
880 if [[ "$optionCompress" = "true" ]]; then # Check if compression option active
881 if checkapp
gzip; then # Check if gzip available
882 cmd_compress
="gzip " && vbm
"cmd_compress:$cmd_compress";
883 cmd_compress_suffix
=".gz" && vbm
"cmd_compress_suffix:$cmd_compress_suffix";
885 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
888 cmd_compress
="tee /dev/null " && vbm
"cmd_compress:$cmd_compress";
889 cmd_compress_suffix
="" && vbm
"cmd_compress_suffix:$cmd_compress_suffix";
890 vbm
"DEBUG:Compression not enabled.";
892 vbm
"Starting magicParseCompressionArg() function.";
893 } # Form compression cmd string and filename suffix
894 magicParseCustomTTL
() {
895 # Desc: Set user-specified TTLs for buffer and script
896 # Usage: magicParseCustomTTL
897 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
898 # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE
899 # Input: vars: bufferTTL (integer), scriptTTL_TE (string)
900 # Output: bufferTTL (integer), scriptTTL_TE (string)
901 # Depends: Bash 5.0.3, yell(), vbm(), validateInput(), showUsage()
903 vbm
"Starting magicParseCustomTTL() function.";
904 # React to '-b, --buffer-ttl' option
905 if [[ "$optionCustomBufferTTL" = "true" ]]; then
906 ## T: Check if argCustomBufferTTL is an integer
907 if validateInput
"$argCustomBufferTTL" "integer"; then
908 ### T: argCustomBufferTTL is an integer
909 bufferTTL
="$argCustomBufferTTL" && vbm
"Custom bufferTTL from -b:$bufferTTL";
911 ### F: argcustomBufferTTL is not an integer
912 yell
"ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1;
914 ## F: do not change bufferTTL
917 # React to '-B, --script-ttl' option
918 if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then
919 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
920 if validateInput
"$argCustomScriptTTL_TE" "time_element"; then
921 ### T: argCustomScriptTTL is a time element
922 scriptTTL_TE
="$argCustomScriptTTL_TE" && vbm
"Custom scriptTTL_TE from -B:$scriptTTL_TE";
924 ### F: argcustomScriptTTL is not a time element
925 yell
"ERROR:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1;
927 ## F: do not change scriptTTL_TE
929 vbm
"Starting magicParseCustomTTL() function.";
930 } # Sets custom script or buffer TTL if specified
932 # Desc: Parses -l option to set label
933 # In : optionLabel, argLabel
935 # Depends: Bash 5.0.3, vbm(), yell()
937 vbm
"STATUS:Started magicParseLabel() function.";
938 # Do nothing if optionLabel not set to true.
939 if [[ ! "$optionLabel" = "true" ]]; then
940 vbm
"STATUS:optionlabel not set to 'true'. Returning early.";
943 # Set label if optionLabel is true
944 if [[ "$optionLabel" = "true" ]]; then
945 label
="_""$argLabel";
946 vbm
"STATUS:Set label:$label";
948 vbm
"STATUS:Finished magicParseLabel() function.";
949 } # Set label used in output file name
950 magicParseProcessStrings
() {
951 # Desc: Processes user-supplied process strings into process commands for appendFileTar().
952 # Usage: magicParseProcessStrings
953 # In : vars: optionProcString optionNoStoreRaw optionStoreRaw argRawFileExt
954 # arry: argProcStrings, argProcFileExts
955 # Out: arry: procStrings, procFileExts
956 # Depends Bash 5.0.3, yell(), vbm()
959 vbm
"STATUS:Starting magicParseProcessStrings() function.";
960 vbm
"var:optionProcString:$optionProcString";
961 vbm
"var:optionNoStoreRaw:$optionNoStoreRaw";
962 vbm
"var:optionStoreRaw:$optionStoreRaw";
963 vbm
"var:argRawFileExt:$argRawFileExt";
964 vbm
"ary:argProcStrings:${argProcStrings[*]}";
965 vbm
"ary:argProcFileExts:${argProcFileExts[*]}"
967 ## Validate argRawFileExt
968 if [[ "$argRawFileExt" =~ ^
[.
][[:alnum
:]]*$
]]; then
969 rawFileExt
="$argRawFileExt";
972 # Add default stdin output file entries for procStrings, procFileExts
973 ## Check if user specified that no raw stdin be saved.
974 if [[ ! "$optionNoStoreRaw" = "true" ]]; then
975 ### T: --no-store-raw not set. Store raw. Append procStrings with cat.
976 #### Append procStrings array
977 procStrings
+=("cat ");
978 #### Check if --store-raw set.
979 if [[ "$optionStoreRaw" = "true" ]]; then
980 ##### T: --store-raw set. Append procFileExts with user-specified file ext
981 procFileExts
+=("$rawFileExt");
983 ##### F: --store-raw not set. Append procFileExts with default ".stdin" file ext
984 ###### Append procFileExts array
985 procFileExts
+=(".stdin");
988 ### F: --no-store-raw set. Do not store raw.
989 #### Do not append procStrings or procFileExts arrays.
993 # Do nothing more if optionProcString not set to true.
994 if [[ ! "$optionProcString" = "true" ]]; then
995 vbm
"STATUS:optionProcString not set to 'true'. Returning early.";
997 # Validate input array indices
998 ## Make sure that argProcStrings and argProcFileExts have same index counts
999 if ! [[ "${#argProcStrings[@]}" -eq "${#argProcFileExts[@]}" ]]; then
1000 yell
"ERROR:Mismatch in number of elements in arrays argProcStrings and argProcFileExts:${#argProcStrings[@]} DNE ${#argProcFileExts[@]}";
1001 yell
"argProcStrings:${argProcStrings[*]}"; yell
"argProcFileExts:${argProcFileExts[*]}"; exit 1; fi;
1002 ## Make sure that no array elements are blank
1003 for element
in "${argProcStrings[@]}"; do
1004 if [[ -z "$element" ]]; then yell
"ERROR:Empty process string specified. Exiting."; exit 1; fi; done
1005 for element
in "${argProcFileExts[@]}"; do
1006 if [[ -z "$element" ]]; then yell
"ERROR:Empty output file extension specified. Exiting."; exit 1; fi; done
1007 ## Make sure that no process string starts with '-' (ex: if only one arg supplied after '-p' option)
1008 for element
in "${argProcStrings[@]}"; do
1009 if [[ "$element" =~ ^
[-][[:print
:]]*$
]] && [[ ! "$element" =~ ^
[[:print
:]]*$
]]; then
1010 yell
"ERROR:Illegal character '-' at start of process string element:\"$element\"";
1012 vbm
"STATUS:Quick check shows argProcStrings and argProcFileExts appear to have valid contents.";
1013 procStrings
=("${argProcStrings[@]}"); # Export process command strings
1014 procFileExts
=("${argProcFileExts[@]}"); # Export process command strings
1015 vbm
"STATUS:Finished magicParseProcessStrings() function.";
1016 } # Validate and save process strings and file extensions to arrays procStrings, procFileExts
1017 magicParseRecipientArgs
() {
1018 # Desc: Parses recipient arguments specified by '-r' option
1019 # Input: vars: optionEncrypt, optionRecipients
1020 # arry: argRecPubKeys from processArguments()
1021 # Output: vars: cmd_encrypt, cmd_encrypt_suffix
1022 # arry: recPubKeysValid, recPubKeysValidStatic
1023 # Depends: processArguments(), yell(), vbm(), checkapp(), checkAgePubkey(), validateInput()
1026 vbm
"Starting magicParseRecipientArgs() function.";
1027 # Check if encryption option active.
1028 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
1029 if checkapp age
; then # Check that age is available.
1030 for pubkey
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
1031 vbm
"DEBUG:Testing pubkey string:$pubkey";
1032 if checkAgePubkey
"$pubkey" && \
1033 ( validateInput
"$pubkey" "ssh_pubkey" || validateInput
"$pubkey" "age_pubkey"); then
1034 #### Form age recipient string
1035 recipients
="$recipients""-r '$pubkey' ";
1036 vbm
"STATUS:Added pubkey for forming age recipient string:""$pubkey";
1037 vbm
"DEBUG:recipients:""$recipients";
1038 #### Add validated pubkey to recPubKeysValid array
1039 recPubKeysValid
+=("$pubkey") && vbm
"DEBUG:recPubkeysValid:pubkey added:$pubkey";
1041 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
1044 vbm
"DEBUG:Finished processing argRecPubKeys array";
1045 vbm
"STATUS:Array of validated pubkeys:${recPubKeysValid[*]}";
1046 recPubKeysValidStatic
=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function
1048 ## Form age command string
1049 cmd_encrypt
="age ""$recipients " && vbm
"cmd_encrypt:$cmd_encrypt";
1050 cmd_encrypt_suffix
=".age" && vbm
"cmd_encrypt_suffix:$cmd_encrypt_suffix";
1052 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
1055 cmd_encrypt
="tee /dev/null " && vbm
"cmd_encrypt:$cmd_encrypt";
1056 cmd_encrypt_suffix
="" && vbm
"cmd_encrypt_suffix:$cmd_encrypt_suffix";
1057 vbm
"DEBUG:Encryption not enabled."
1059 # Catch case if '-e' is set but '-r' or '-R' is not
1060 if [[ "$optionEncrypt" = "true" ]] && [[ ! "$optionRecipients" = "true" ]]; then
1061 yell
"ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
1062 # Catch case if '-r' or '-R' set but '-e' is not
1063 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
1064 yell
"ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
1065 vbm
"Finished magicParseRecipientArgs() function.";
1066 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
1067 magicParseRecipientDir
() {
1068 # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory")
1069 # Inputs: vars: optionEncrypt, optionRecDir, argRecDir,
1070 # arry: recPubKeysValid
1071 # Outputs: arry: recPubKeysValid
1072 # Depends: processArguments(), yell(), vbm(), validateInput(), checkAgePubkey()
1073 local recipientDir recFileLine updateRecipients
1074 declare -a candRecPubKeysValid
1076 vbm
"Starting magicParseRecipientDir() function.";
1077 # Check that '-e' and '-R' set
1078 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
1079 ### Check that argRecDir is a directory.
1080 if [[ -d "$argRecDir" ]]; then
1081 recipientDir
="$argRecDir" && vbm
"STATUS:Recipient watch directory detected:\"$recipientDir\"";
1082 #### Initialize variable indicating outcome of pubkey review
1083 unset updateRecipients
1084 #### Add existing recipients
1085 candRecPubKeysValid
=("${recPubKeysValidStatic[@]}");
1086 #### Parse files in recipientDir
1087 for file in "$recipientDir"/*; do
1088 ##### Read first line of each file
1089 recFileLine
="$(head -n1 "$file")" && vbm
"STATUS:Checking if pubkey:\"$recFileLine\"";
1090 ##### check if first line is a valid pubkey
1091 if checkAgePubkey
"$recFileLine" && \
1092 ( validateInput
"$recFileLine" "ssh_pubkey" || validateInput
"$recFileLine" "age_pubkey"); then
1093 ###### T: add candidate pubkey to candRecPubKeysValid
1094 candRecPubKeysValid
+=("$recFileLine") && vbm
"STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\"";
1096 ###### F: throw warning;
1097 yell
"ERROR:Invalid recipient file detected. Not modifying recipient list."
1098 updateRecipients
="false";
1101 #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected
1102 if ! [[ "$updateRecipients" = "false" ]]; then
1103 recPubKeysValid
=("${candRecPubKeysValid[@]}") && vbm
"STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\"";
1106 yell
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
1109 # Handle case if '-R' set but '-e' not set
1110 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
1111 yell
"ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi;
1112 vbm
"Finished magicParseRecipientDir() function.";
1113 } # Update recPubKeysValid with argRecDir
1114 magicSetScriptTTL
() {
1115 #Desc: Sets script_TTL seconds from provided time_element string argument
1116 #Usage: magicSetScriptTTL [str time_element]
1117 #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour")
1118 #Output: var: scriptTTL (integer seconds)
1119 #Depends: timeUntilNextHour, timeUntilNextDay
1120 local argTimeElement
1122 vbm
"Starting magicSetScriptTTL() function.";
1123 argTimeElement
="$1";
1124 if [[ "$argTimeElement" = "day" ]]; then
1125 # Set script lifespan to end at start of next day
1126 if ! scriptTTL
="$(timeUntilNextDay)"; then # sets scriptTTL, then checks exit code
1127 if [[ "$scriptTTL" -eq 0 ]]; then
1128 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
1130 yell
"ERROR: timeUntilNextDay exit code $?"; exit 1;
1133 elif [[ "$argTimeElement" = "hour" ]]; then
1134 # Set script lifespan to end at start of next hour
1135 if ! scriptTTL
="$(timeUntilNextHour)"; then # sets scriptTTL, then checks exit code
1136 if [[ "$scriptTTL" -eq 0 ]]; then
1137 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
1139 yell
"ERROR: timeUntilNextHour exit code $?"; exit 1;
1143 yell
"ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
1145 vbm
"Finished magicSetScriptTTL() function.";
1146 } # Set scriptTTL in seconds until next (day|hour).
1147 magicWriteVersion
() {
1148 # Desc: Appends time-stamped VERSION to pathout_tar
1149 # Usage: magicWriteVersion
1150 # Input: vars: pathout_tar, dir_tmp
1151 # Input: vars: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname
1152 # Input: array: recPubKeysValid
1153 # Output: appends tar (pathout_tar)
1154 # Depends: bash 5.0.3, dateTimeShort(), appendArgTar()
1155 local fileoutVersion contentVersion pubKeyIndex pubKeyIndex
1157 vbm
"Starting magicWriteVersion() function.";
1158 # Set VERSION file name
1159 fileoutVersion
="$(dateTimeShort)..VERSION";
1161 # Gather VERSION data in contentVersion
1162 contentVersion
="scriptVersion=$scriptVersion";
1163 #contentVersion="$contentVersion""\\n";
1164 contentVersion
="$contentVersion""\\n""scriptName=$scriptName";
1165 contentVersion
="$contentVersion""\\n""scriptURL=$scriptURL";
1166 contentVersion
="$contentVersion""\\n""ageVersion=$ageVersion";
1167 contentVersion
="$contentVersion""\\n""ageURL=$ageURL";
1168 contentVersion
="$contentVersion""\\n""date=$(date --iso-8601=seconds)";
1169 contentVersion
="$contentVersion""\\n""hostname=$scriptHostname";
1170 ## Add list of recipient pubkeys
1171 for pubkey
in "${recPubKeysValid[@]}"; do
1173 contentVersion
="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1175 ## Process newline escapes
1176 contentVersion
="$(echo -e "$contentVersion")"
1178 # Write contentVersion as file fileoutVersion and write-append to pathout_tar
1179 appendArgTar
"$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp";
1180 vbm
"Finished magicWriteVersion() function.";
1181 } # write version data to pathout_tar via appendArgTar()
1182 magicProcessWriteBuffer
() {
1183 # Desc: process and write buffer
1184 # In : vars: bufferTTL bufferTTL_STR scriptHostname label dir_tmp SECONDS
1186 # Out: file:(pathout_tar)
1187 # Depends: Bash 5.0.3, date 8.30, yell(), vbm(), dateTimeShort(),
1188 ### Note: These arrays should all have the same number of elements:
1189 ### pathouts, fileouts, procFileExts, procStrings
1191 local fn timeBufferStartLong timeBufferStart fileoutBasename
1192 local -a fileouts pathouts
1193 local writeCmd1 writeCmd2 writeCmd3 writeCmd4
1195 vbm
"DEBUG:STATUS:$fn:Started magicProcessWriteBuffer().";
1196 # Debug:Get function name
1197 fn
="${FUNCNAME[0]}";
1199 # Determine file paths (time is start of buffer period)
1200 ## Calculate start time
1201 timeBufferStartLong
="$(date --date="$bufferTTL seconds ago
" --iso-8601=seconds)" && \
1202 vbm
"timeBufferStartLong:$timeBufferStartLong";
1203 timeBufferStart
="$(dateTimeShort "$timeBufferStartLong" )" && \
1204 vbm
"timeBufferStart:$timeBufferStart"; # Note start time YYYYmmddTHHMMSS+zzzz (no separators)
1205 ## Set common basename
1206 fileoutBasename
="$timeBufferStart""--""$bufferTTL_STR""..""$scriptHostname""$label" && \
1207 vbm
"STATUS:Set fileoutBasename to:$fileoutBasename";
1208 ## Determine output file name array
1209 ### in: fileOutBasename cmd_compress_suffix cmd_encrypt_suffix procFileExts
1210 for fileExt
in "${procFileExts[@]}"; do
1211 fileouts
+=("$fileoutBasename""$fileExt""$cmd_compress_suffix""$cmd_encrypt_suffix") && \
1212 vbm
"STATUS:Added $fileExt to fileouts:${fileouts[*]}";
1214 for fileName
in "${fileouts[@]}"; do
1215 pathouts
+=("$dir_tmp"/"$fileName") && \
1216 vbm
"STATUS:Added $fileName to pathouts:${pathouts[*]}";
1218 ## Update pathout_tar
1221 # Process and write buffers to dir_tmp
1222 ## Prepare command strings
1223 writeCmd1
="printf \"%s\\\\n\" \"\${buffer[@]}\""; # printf "%s\\n" "${buffer[@]}"
1224 #writeCmd2="" # NOTE: Specified by parsing array procStrings
1225 writeCmd3
="$cmd_compress";
1226 writeCmd4
="$cmd_encrypt";
1228 ## Process buffer and write to dir_tmp
1229 for index
in "${!pathouts[@]}"; do
1230 writeCmd2
="${procStrings[$index]}"
1231 eval "$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" >> "${pathouts[$index]}";
1234 # Append dir_tmp files to pathout_tar
1235 wait; # Wait to avoid collision with older magicProcessWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
1236 for index
in "${!pathouts[@]}"; do
1237 appendFileTar
"${pathouts[$index]}" "${fileouts[$index]}" "$pathout_tar" "$dir_tmp";
1240 # Remove secured chunks from dir_tmp
1241 for path
in "${pathouts[@]}"; do
1245 vbm
"DEBUG:STATUS:$fn:Finished magicProcessWriteBuffer().";
1246 } # Process and Write buffer
1250 processArguments
"$@";
1251 ## Determine working directory
1252 magicInitWorkingDir
; # Sets dir_tmp from argTempDirPriority
1253 ## Set output encryption and compression option strings
1254 ### React to "-e" and "-r" ("encryption recipients") options
1255 magicParseRecipientArgs
; # Updates recPubKeysValid, cmd_encrypt[_suffix] from argRecPubKeys
1256 ### React to "-R" ("recipient directory") option
1257 magicParseRecipientDir
; # Updates recPubKeysValid
1258 ### React to "-c" ("compression") option
1259 magicParseCompressionArg
; # Updates cmd_compress[_suffix]
1260 ## React to "-b" and "-B" (custom buffer and script TTL) options
1261 magicParseCustomTTL
; # Sets custom scriptTTL_TE and/or bufferTTL if specified
1262 ## React to "-p" (user-supplied process command and file extension strings) options
1263 magicParseProcessStrings
; # Sets arrays: procStrings, procFileExts
1264 ## React to "-l" (output file label) option
1265 magicParseLabel
; # sets label (ex: "_location")
1267 # Perform secondary setup operations
1268 ## Set script lifespan (scriptTTL from scriptTTL_TE)
1269 magicSetScriptTTL
"$scriptTTL_TE";
1270 ## File name substring (ISO-8601 duration from bufferTTL)
1271 bufferTTL_STR
="$(timeDuration "$bufferTTL")" && vbm
"DEBUG:bufferTTL_STR:$bufferTTL_STR";
1272 ## Init temp working dir
1273 try mkdir
"$dir_tmp" && vbm
"DEBUG:Working dir created at dir_tmp:$dir_tmp";
1274 ## Initialize output tar (set pathout_tar)
1277 # Check vital apps, files, dirs
1278 if ! checkapp
tar && ! checkdir
"$dirOut" "dir_tmp"; then
1279 yell
"ERROR:Critical components missing.";
1280 displayMissing
; yell
"Exiting."; exit 1; fi
1282 # MAIN LOOP: Run until script TTL seconds pass
1284 while [[ $SECONDS -lt "scriptTTL" ]]; do
1285 vbm
"DEBUG:Starting buffer round:$bufferRound";
1286 bufferTOD
="$((SECONDS + bufferTTL))"; # Set buffer round time-of-death
1287 # Consume stdin to fill buffer until buffer time-of-death (TOD) arrives
1288 while read -r -t "$bufferTTL" line
&& [[ $SECONDS -lt "$bufferTOD" ]]; do
1289 # Append line to buffer array
1292 # Create dir_tmp if missing
1293 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
1294 # Update encryption recipient array
1295 magicParseRecipientDir
; # Update recPubKeysValid with argRecDir
1296 # Export buffer to asynchronous processing.
1297 magicProcessWriteBuffer
&
1298 unset buffer
; # Clear buffer array for next bufferRound
1299 # Increment buffer round
1305 try
rm -r "$dir_tmp" && vbm
"Removed dir_tmp:$dir_tmp";
1307 vbm
"STATUS:Main function finished.";
1310 #===END Declare local script functions===
1311 #==END Define script parameters==
1313 #==BEGIN Perform work and exit==
1314 main
"$@" # Run main function.
1316 #==END Perform work and exit==
1318 # Author: Steven Baltakatei Sandoval;