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.32";         # Define version of script. 
  15 scriptURL
="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define website hosting this script. 
  16 scriptTimeStartEpoch
="$(date +%s)"; # Save start time of script in epoch seconds 
  17 scriptTimeStart
="$(date +%Y%m%dT%H%M%S.%N)"; # YYYYmmddTHHMMSS.NNNNNNNNN 
  18 scriptHostname
=$
(hostname
);     # Save hostname of system running this script. 
  19 PATH
="$HOME/.local/bin:$PATH";  # Add "$(systemd-path user-binaries)" path in case user apps saved there 
  20 ageVersion
="1.0.0-beta2";       # Define version of age (encryption program) 
  21 ageURL
="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age. 
  24 declare -a buffer          
# array for storing while read buffer 
  25 declare -a argRecPubKeys   
# array for processArguments function 
  26 declare -a recPubKeysValid 
# array for storing both '-r' and '-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) optionRecArg
="true"; argRecPubKeys
+=("$2"); vbm 
"STATUS:pubkey added:""$2"; shift;; # Add recipients 
  52             -R | 
--recipient-dir) optionRecDir
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir 
  53             -c | 
--compress) optionCompress
="true"; vbm 
"DEBUG :Compressed output mode enabled.";; # Enable compression 
  54             -z | 
--time-zone) try setTimeZoneEV 
"$2"; shift;; # Set timestamp timezone 
  55             -t | 
--temp-dir) optionTmpDir
="true" && argTempDirPriority
="$2"; shift;; # Set time zone 
  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 \"$3\" for output of processing string added:\"$2\""; 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.";; 
  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]:$0:""$*" 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: Writes first argument to temporary file with arguments as options, then appends file to tar 
 231     # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 233     # Input: arg1: data to be written 
 234     #        arg2: file name of file to be inserted into tar 
 235     #        arg3: tar archive path (must exist first) 
 236     #        arg4: temporary working dir 
 237     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 238     # Output: file written to disk 
 239     # Example: decrypt multiple large files in parallel 
 240     #          appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 241     #          appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 242     #          appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 243     # Depends: bash 5, tar 1, yell() 
 244     # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 
 246     local fn fileName tarPath tmpDir cmd0 cmd1 cmd2 cmd3 cmd4
 
 250     #yell "DEBUG:STATUS:$fn:Finished appendArgTar()." 
 253     if ! [ -z "$2" ]; then fileName
="$2"; else yell 
"ERROR:$fn:Not enough arguments."; exit 1; fi 
 255     # Check tar path is a file 
 256     if [ -f "$3" ]; then tarPath
="$3"; else yell 
"ERROR:$fn:Tar archive arg not a file."; exit 1; fi 
 259     if ! [ -z "$4" ]; then tmpDir
="$4"; else yell 
"ERROR:$fn:No temporary working dir set."; exit 1; fi 
 261     # Set command strings 
 262     if ! [ -z "$5" ]; then cmd1
="$5"; else cmd1
="cat "; fi # command string 1 
 263     if ! [ -z "$6" ]; then cmd2
="$6"; else cmd2
="cat "; fi # command string 2 
 264     if ! [ -z "$7" ]; then cmd3
="$7"; else cmd3
="cat "; fi # command string 3 
 265     if ! [ -z "$8" ]; then cmd4
="$8"; else cmd4
="cat "; fi # command string 4 
 271     # yell "DEBUG:STATUS:$fn:cmd0:$cmd0" 
 272     # yell "DEBUG:STATUS:$fn:cmd1:$cmd1" 
 273     # yell "DEBUG:STATUS:$fn:cmd2:$cmd2" 
 274     # yell "DEBUG:STATUS:$fn:cmd3:$cmd3" 
 275     # yell "DEBUG:STATUS:$fn:cmd4:$cmd4" 
 276     # yell "DEBUG:STATUS:$fn:fileName:$fileName" 
 277     # yell "DEBUG:STATUS:$fn:tarPath:$tarPath" 
 278     # yell "DEBUG:STATUS:$fn:tmpDir:$tmpDir" 
 280     # Write to temporary working dir 
 281     eval "$cmd0 | $cmd1 | $cmd2 | $cmd3 | $cmd4" > "$tmpDir"/"$fileName"; 
 284     try 
tar --append --directory="$tmpDir" --file="$tarPath" "$fileName"; 
 285     #yell "DEBUG:STATUS:$fn:Finished appendArgTar()." 
 286 } # Append Bash var to file appended to Tar archive 
 288     # Desc: Appends [processed] file to tar 
 289     # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([process cmd]) 
 291     # Input: arg1: path of file to be (processed and) written 
 292     #        arg2: name to use for file inserted into tar 
 293     #        arg3: tar archive path (must exist first) 
 294     #        arg4: temporary working dir 
 295     #        arg5: (optional) command string to process file (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 296     # Output: file written to disk 
 297     # Example: decrypt multiple large files in parallel 
 298     #          appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 299     #          appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 300     #          appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 301     # Depends: bash 5.0.3, tar 1.30, cat 8.30, yell() 
 302     local fn fileName tarPath tmpDir
 
 306     #yell "DEBUG :STATUS:$fn:Started appendFileTar()." 
 309     if ! [ -z "$2" ]; then fileName
="$2"; else yell 
"ERROR:$fn:Not enough arguments."; exit 1; fi 
 310     # Check tar path is a file 
 311     if [ -f "$3" ]; then tarPath
="$3"; else yell 
"ERROR:$fn:Tar archive arg not a file:$3"; exit 1; fi 
 313     if ! [ -z "$4" ]; then tmpDir
="$4"; else yell 
"ERROR:$fn:No temporary working dir set."; exit 1; fi 
 314     # Set command strings 
 315     if ! [ -z "$5" ]; then cmd1
="$5"; else cmd1
="cat "; fi # command string 
 317     # Input command string 
 320     # Write to temporary working dir 
 321     eval "$cmd0 | $cmd1" > "$tmpDir"/"$fileName"; 
 324     try 
tar --append --directory="$tmpDir" --file="$tarPath" "$fileName"; 
 325     #yell "DEBUG :STATUS:$fn:Finished appendFileTar()." 
 326 } # Append [processed] file to Tar archive 
 328     # Desc: Checks if string is an age-compatible pubkey 
 329     # Usage: checkAgePubkey [str pubkey] 
 331     # Input: arg1: string 
 332     # Output: return code 0: string is age-compatible pubkey 
 333     #         return code 1: string is NOT an age-compatible pubkey 
 334     #         age stderr (ex: there is stderr if invalid string provided) 
 335     # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 ) 
 339     if echo "test" | age 
-a -r "$argPubkey" 1>/dev
/null
; then 
 346     # Desc: Checks that a valid tar archive exists, creates one otherwise 
 347     # Usage: checkMakeTar [ path ] 
 349     # Input: arg1: path of tar archive 
 350     # Output: exit code 0 : tar readable 
 351     #         exit code 1 : tar missing; created 
 352     #         exit code 2 : tar not readable; moved; replaced 
 353     # Depends: bash 5, date 8, tar 1, try() 
 354     local pathTar returnFlag0 returnFlag1 returnFlag2
 
 357     # Check if file is a valid tar archive 
 358     if tar --list --file="$pathTar" 1>/dev
/null 
2>&1; then 
 359         ## T1: return success 
 360         returnFlag0
="tar valid"; 
 361     elif { sleep 2; tar --list --file="$pathTar" 1>/dev
/null 
2>&1; }; then 
 362         ## F1: Check tar archive again after 2-second sleep 
 363         returnFlag0
="tar valid"; 
 365         ## F2-1: Check if file exists 
 366         if [[ -f "$pathTar" ]]; then 
 368             try 
mv "$pathTar" "$pathTar""--broken--""$(date +%Y%m%dT%H%M%S%z)" && \
 
 369                 returnFlag1
="tar moved"; 
 374         ## F2-1: Create tar archive, return 0 
 375         try 
tar --create --file="$pathTar" --files-from=/dev
/null 
&& \
 
 376             returnFlag2
="tar created"; 
 379     # Determine function return code 
 380     if [[ "$returnFlag0" = "tar valid" ]]; then 
 382     elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then 
 383         return 1; # tar missing so created 
 384     elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then 
 385         return 2; # tar not readable so moved; replaced 
 387 } # checks if arg1 is tar; creates one otherwise 
 389     # Desc: Date without separators (YYYYmmdd) 
 390     # Usage: dateShort ([str date]) 
 392     # Input: arg1: 'date'-parsable timestamp string (optional) 
 393     # Output: stdout: date (ISO-8601, no separators) 
 394     # Depends: bash 5.0.3, date 8.30, yell() 
 395     local argTime timeCurrent timeInput dateCurrentShort
 
 399     timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 400     # Decide to parse current or supplied date 
 401     ## Check if time argument empty 
 402     if [[ -z "$argTime" ]]; then 
 403         ## T: Time argument empty, use current time 
 404         timeInput
="$timeCurrent"; 
 406         ## F: Time argument exists, validate time 
 407         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 408             ### T: Time argument is valid; use it 
 409             timeInput
="$argTime"; 
 411             ### F: Time argument not valid; exit 
 412             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 415     # Construct and deliver separator-les date string     
 416     dateCurrentShort
="$(date -d "$timeInput" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. 
 417     echo "$dateCurrentShort"; 
 420     # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) 
 421     # Usage: dateTimeShort ([str date]) 
 423     # Input: arg1: 'date'-parsable timestamp string (optional) 
 424     # Output: stdout: timestamp (ISO-8601, no separators) 
 426     local argTime timeCurrent timeInput timeCurrentShort
 
 430     timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 431     # Decide to parse current or supplied date 
 432     ## Check if time argument empty 
 433     if [[ -z "$argTime" ]]; then 
 434         ## T: Time argument empty, use current time 
 435         timeInput
="$timeCurrent"; 
 437         ## F: Time argument exists, validate time 
 438         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 439             ### T: Time argument is valid; use it 
 440             timeInput
="$argTime"; 
 442             ### F: Time argument not valid; exit 
 443             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 446     # Construct and deliver separator-les date string 
 447     timeCurrentShort
="$(date -d "$timeInput" +%Y%m%dT%H%M%S%z)"; 
 448     echo "$timeCurrentShort"; 
 449 } # Get YYYYmmddTHHMMSS±zzzz 
 451     # Desc: Set time zone environment variable TZ 
 452     # Usage: setTimeZoneEV arg1 
 454     # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") 
 455     #        TZDIR env var (optional; default: "/usr/share/zoneinfo") 
 457     #         exit code 0 on success 
 458     #         exit code 1 on incorrect number of arguments 
 459     #         exit code 2 if unable to validate arg1 
 460     # Depends: yell(), printenv 8.30, bash 5.0.3 
 461     # Tested on: Debian 10 
 462     local tzDir returnState argTimeZone
 
 465     if ! [[ $# -eq 1 ]]; then 
 466         yell 
"ERROR:Invalid argument count."; 
 470     # Read TZDIR env var if available 
 471     if printenv TZDIR 
1>/dev
/null 
2>&1; then 
 472         tzDir
="$(printenv TZDIR)"; 
 474         tzDir
="/usr/share/zoneinfo"; 
 478     if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then 
 479         yell 
"ERROR:Invalid time zone argument."; 
 482     # Export ARG1 as TZ environment variable 
 483         TZ
="$argTimeZone" && export TZ 
&& returnState
="true"; 
 486     # Determine function return code 
 487     if [ "$returnState" = "true" ]; then 
 490 } # Exports TZ environment variable 
 492     yell 
"$scriptVersion" 
 493 } # Display script version. 
 495     # Desc: Given seconds, output ISO-8601 duration string 
 496     # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information 
 497     # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) 
 498     # Usage: timeDuration [1:seconds] ([2:precision]) 
 500     # Input: arg1: seconds as base 10 integer >= 0  (ex: 3601) 
 501     #        arg2: precision level (optional; default=2) 
 502     # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") 
 503     #         exit code 0: success 
 504     #         exit code 1: error_input 
 505     #         exit code 2: error_unknown 
 506     # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' 
 507     # Depends: date 8, bash 5, yell, 
 508     local argSeconds argPrecision precision returnState remainder
 
 509     local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
 
 510     local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
 
 511     local witherPrecision output
 
 512     local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
 
 514     argSeconds
="$1"; # read arg1 (seconds) 
 515     argPrecision
="$2"; # read arg2 (precision) 
 516     precision
=2; # set default precision 
 518     # Check that between one and two arguments is supplied 
 519     if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then 
 520         yell 
"ERROR:Invalid number of arguments:$# . Exiting."; 
 521         returnState
="error_input"; fi 
 523     # Check that argSeconds provided 
 524     if [[ $# -ge 1 ]]; then 
 525         ## Check that argSeconds is a positive integer 
 526         if [[ "$argSeconds" =~ ^
[[:digit
:]]+$ 
]]; then 
 529             yell 
"ERROR:argSeconds not a digit."; 
 530             returnState
="error_input"; 
 533         yell 
"ERROR:No argument provided. Exiting."; 
 537     # Consider whether argPrecision was provided 
 538     if  [[ $# -eq 2 ]]; then 
 539         # Check that argPrecision is a positive integer 
 540         if [[ "$argPrecision" =~ ^
[[:digit
:]]+$ 
]] && [[ "$argPrecision" -gt 0 ]]; then 
 541         precision
="$argPrecision"; 
 543             yell 
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; 
 544             returnState
="error_input"; 
 550     remainder
="$argSeconds" ; # seconds 
 551     ## Calculate full years Y, update remainder 
 552     fullYears
=$
(( remainder 
/ (365*24*60*60) )); 
 553     remainder
=$
(( remainder 
- (fullYears
*365*24*60*60) )); 
 554     ## Calculate full months M, update remainder 
 555     fullMonths
=$
(( remainder 
/ (30*24*60*60) )); 
 556     remainder
=$
(( remainder 
- (fullMonths
*30*24*60*60) )); 
 557     ## Calculate full days D, update remainder 
 558     fullDays
=$
(( remainder 
/ (24*60*60) )); 
 559     remainder
=$
(( remainder 
- (fullDays
*24*60*60) )); 
 560     ## Calculate full hours H, update remainder 
 561     fullHours
=$
(( remainder 
/ (60*60) )); 
 562     remainder
=$
(( remainder 
- (fullHours
*60*60) )); 
 563     ## Calculate full minutes M, update remainder 
 564     fullMinutes
=$
(( remainder 
/ (60) )); 
 565     remainder
=$
(( remainder 
- (fullMinutes
*60) )); 
 566     ## Calculate full seconds S, update remainder 
 567     fullSeconds
=$
(( remainder 
/ (1) )); 
 568     remainder
=$
(( remainder 
- (remainder
*1) )); 
 569     ## Check which fields filled 
 570     if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi 
 571     if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi 
 572     if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi 
 573     if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi 
 574     if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi 
 575     if [[ $fullSeconds -ge 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi 
 577     ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) 
 578     witherPrecision
="false" 
 581     if $hasYears && [[ $precision -gt 0 ]]; then 
 583         witherPrecision
="true"; 
 585         displayYears
="false"; 
 587     if $witherPrecision; then ((precision--
)); fi; 
 590     if $hasMonths && [[ $precision -gt 0 ]]; then 
 591         displayMonths
="true"; 
 592         witherPrecision
="true"; 
 594         displayMonths
="false"; 
 596     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 597         displayMonths
="true"; 
 599     if $witherPrecision; then ((precision--
)); fi; 
 602     if $hasDays && [[ $precision -gt 0 ]]; then 
 604         witherPrecision
="true"; 
 608     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 611     if $witherPrecision; then ((precision--
)); fi; 
 614     if $hasHours && [[ $precision -gt 0 ]]; then 
 616         witherPrecision
="true"; 
 618         displayHours
="false"; 
 620     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 623     if $witherPrecision; then ((precision--
)); fi; 
 626     if $hasMinutes && [[ $precision -gt 0 ]]; then 
 627         displayMinutes
="true"; 
 628         witherPrecision
="true"; 
 630         displayMinutes
="false"; 
 632     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 633         displayMinutes
="true"; 
 635     if $witherPrecision; then ((precision--
)); fi; 
 639     if $hasSeconds && [[ $precision -gt 0 ]]; then 
 640         displaySeconds
="true"; 
 641         witherPrecision
="true"; 
 643         displaySeconds
="false"; 
 645     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 646         displaySeconds
="true"; 
 648     if $witherPrecision; then ((precision--
)); fi; 
 650     ## Determine whether or not the "T" separator is needed to separate date and time elements 
 651     if ( $displayHours || 
$displayMinutes || 
$displaySeconds); then 
 652         displayDateTime
="true"; else displayDateTime
="false"; fi 
 654     ## Construct duration output string 
 656     if $displayYears; then 
 657         output
=$output$fullYears"Y"; fi 
 658     if $displayMonths; then 
 659         output
=$output$fullMonths"M"; fi 
 660     if $displayDays; then 
 661         output
=$output$fullDays"D"; fi 
 662     if $displayDateTime; then 
 663         output
=$output"T"; fi 
 664     if $displayHours; then 
 665         output
=$output$fullHours"H"; fi 
 666     if $displayMinutes; then 
 667         output
=$output$fullMinutes"M"; fi 
 668     if $displaySeconds; then 
 669         output
=$output$fullSeconds"S"; fi 
 671     ## Output duration string to stdout 
 672     echo "$output" && returnState
="true"; 
 674     #===Determine function return code=== 
 675     if [ "$returnState" = "true" ]; then 
 677     elif [ "$returnState" = "error_input" ]; then 
 681         yell 
"ERROR:Unknown"; 
 685 } # Get duration (ex: PT10M4S ) 
 687     # Desc: Report seconds until next day. 
 689     # Output: stdout: integer seconds until next day 
 690     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 691     # Usage: timeUntilNextDay 
 692     # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi 
 693     # Depends: date 8, echo 8, yell, try 
 695     local returnState timeCurrent timeNextDay secondsUntilNextDay returnState
 
 696     timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 697     timeNextDay
="$(date -d "$timeCurrent next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. 
 698     secondsUntilNextDay
="$(( $(date +%s -d "$timeNextDay") - $(date +%s -d "$timeCurrent") ))" ; # Calculate seconds until closest future midnight (res. 1 second). 
 699     if [[ "$secondsUntilNextDay" -gt 0 ]]; then 
 701     elif [[ "$secondsUntilNextDay" -eq 0 ]]; then 
 702         returnState
="warning_zero"; 
 703         yell 
"WARNING:Reported time until next day exactly zero."; 
 704     elif [[ "$secondsUntilNextDay" -lt 0 ]]; then 
 705         returnState
="warning_negative"; 
 706         yell 
"WARNING:Reported time until next day is negative."; 
 709     try 
echo "$secondsUntilNextDay"; # Report 
 711     # Determine function return code 
 712     if [[ "$returnState" = "true" ]]; then 
 714     elif [[ "$returnState" = "warning_zero" ]]; then 
 716     elif [[ "$returnState" = "warning_negative" ]]; then 
 719 } # Report seconds until next day 
 721     # Desc: Report seconds until next hour 
 723     # Output: stdout: integer seconds until next hour 
 724     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 725     # Usage: timeUntilNextHour 
 726     # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi 
 728     local returnState timeCurrent timeNextHour secondsUntilNextHour
 
 729     timeCurrent
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 730     timeNextHour
="$(date -d "$timeCurrent next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. 
 731     secondsUntilNextHour
="$(( $(date +%s -d "$timeNextHour") - $(date +%s -d "$timeCurrent") ))"; # Calculate seconds until next hour (res. 1 second). 
 732     if [[ "$secondsUntilNextHour" -gt 0 ]]; then 
 734     elif [[ "$secondsUntilNextHour" -eq 0 ]]; then 
 735         returnState
="warning_zero"; 
 736         yell 
"WARNING:Reported time until next hour exactly zero."; 
 737     elif [[ "$secondsUntilNextHour" -lt 0 ]]; then 
 738         returnState
="warning_negative"; 
 739         yell 
"WARNING:Reported time until next hour is negative."; 
 742     try 
echo "$secondsUntilNextHour"; # Report 
 744     # Determine function return code 
 745     if [[ "$returnState" = "true" ]]; then 
 747     elif [[ "$returnState" = "warning_zero" ]]; then 
 749     elif [[ "$returnState" = "warning_negative" ]]; then 
 752 } # Report seconds until next hour 
 754     # Desc: Validates Input 
 755     # Usage: validateInput [str input] [str input type] 
 757     # Input: arg1: string to validate 
 758     #        arg2: string specifying input type (ex:"ssh_pubkey") 
 759     # Output: return code 0: if input string matched specified string type 
 760     # Depends: bash 5, yell() 
 762     local fn argInput argType
 
 770     if [[ $# -gt 2 ]]; then yell 
"ERROR:$0:$fn:Too many arguments."; exit 1; fi; 
 773     if [[ -z "$argInput" ]]; then return 1; fi 
 777     ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") 
 778     if [[ "$argType" = "ssh_pubkey" ]]; then 
 779         if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\ 
]*[[:alnum
:]+/=]*$ 
]]; then 
 783     ### Check for age1[:bech32:] 
 784     if [[ "$argType" = "age_pubkey" ]]; then 
 785         if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$ 
]]; then 
 789     if [[ "$argType" = "integer" ]]; then 
 790         if [[ "$argInput" =~ ^
[[:digit
:]]*$ 
]]; then 
 793     ## time element (year, month, week, day, hour, minute, second) 
 794     if [[ "$argType" = "time_element" ]]; then 
 795         if [[ "$argInput" = "year" ]] || \
 
 796                [[ "$argInput" = "month" ]] || \
 
 797                [[ "$argInput" = "week" ]] || \
 
 798                [[ "$argInput" = "day" ]] || \
 
 799                [[ "$argInput" = "hour" ]] || \
 
 800                [[ "$argInput" = "minute" ]] || \
 
 801                [[ "$argInput" = "second" ]]; then 
 804     # Return error if no condition matched. 
 806 } # Validates strings 
 808 magicInitWorkingDir
() { 
 809     # Desc: Determine temporary working directory from defaults or user input 
 810     # Usage: magicInitWorkingDir 
 811     # Input:  vars: optionTmpDir, argTempDirPriority, dirTmpDefault 
 812     # Input:  vars: scriptTimeStart 
 813     # Output: vars: dir_tmp 
 814     # Depends: bash 5.0.3, processArguments(), vbm(), yell() 
 815     # Parse '-t' option (user-specified temporary working dir) 
 816     ## Set dir_tmp_parent to user-specified value if specified 
 817     local fn dir_tmp_parent
 
 822     vbm 
"STATUS:$fn:Starting magicInitWorkingDir() function."; 
 823     if [[ "$optionTmpDir" = "true" ]]; then 
 824         if [[ -d "$argTempDirPriority" ]]; then 
 825             dir_tmp_parent
="$argTempDirPriority";  
 827             yell 
"WARNING:$fn:Specified temporary working directory not valid:$argTempDirPriority"; 
 828             exit 1; # Exit since user requires a specific temp dir and it is not available. 
 831     ## Set dir_tmp_parent to default or fallback otherwise 
 832         if [[ -d "$dirTmpDefault" ]]; then 
 833             dir_tmp_parent
="$dirTmpDefault"; 
 834         elif [[ -d /tmp 
]]; then 
 835             yell 
"WARNING:$fn:$dirTmpDefault not available. Falling back to /tmp ."; 
 836             dir_tmp_parent
="/tmp"; 
 838             yell 
"ERROR:$fn:No valid working directory available. Exiting."; 
 842     ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart) 
 843     dir_tmp
="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm 
"DEBUG :$fn:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main(). 
 844     vbm 
"STATUS:$fn:Finished magicInitWorkingDir() function."; 
 846 magicInitCheckTar
() { 
 847     # Desc: Initializes or checks output tar 
 848     # input: vars: dirOut, bufferTTL, cmd_encrypt_suffix, cmd_compress_suffix 
 849     # input: vars: scriptHostname 
 850     # output: vars: pathout_tar 
 851     # depends: Bash 5.0.3, vbm(), dateShort(), checkMakeTar(), magicWriteVersion() 
 852     local fn checkMakeTarES
 
 857     vbm 
"STATUS:$fn:Starting magicInitCheckTar() function."; 
 859     pathout_tar
="$dirOut"/"$(dateShort "$
(date --date="$bufferTTL seconds ago" --iso-8601=seconds
)")"..
"$scriptHostname""$label""$cmd_compress_suffix""$cmd_encrypt_suffix".
tar && \
 
 860         vbm 
"STATUS:$fn:Set pathout_tar to:$pathout_tar"; 
 861     # Validate pathout_tar as tar. 
 862     checkMakeTar 
"$pathout_tar"; checkMakeTarES
="$?"; 
 863     ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) 
 864     vbm 
"STATUS:$fn:exit status before magicWriteVersion:$checkMakeTarES" 
 865     if [[ "$checkMakeTarES" -eq 1 ]] || 
[[ "$checkMakeTarES" -eq 2 ]]; then magicWriteVersion
; fi 
 866     vbm 
"STATUS:$fn:Finished magicInitCheckTar() function."; 
 867 } # Initialize tar, set pathout_tar 
 868 magicParseCompressionArg
() { 
 869     # Desc: Parses compression arguments specified by '-c' option 
 870     # Input:  vars: optionCompress 
 871     # Output: cmd_compress, cmd_compress_suffix 
 872     # Depends: processArguments(), vbm(), checkapp(), gzip 1.9 
 878     vbm 
"STATUS:$fn:Starting magicParseCompressionArg() function."; 
 879     if [[ "$optionCompress" = "true" ]]; then # Check if compression option active 
 880         if checkapp 
gzip; then # Check if gzip available 
 881             cmd_compress
="gzip " && vbm 
"STATUS:$fn:cmd_compress:$cmd_compress"; 
 882             cmd_compress_suffix
=".gz" && vbm 
"STATUS:$fn:cmd_compress_suffix:$cmd_compress_suffix"; 
 884             yell 
"ERROR:$fn:Compression enabled but \"gzip\" not found. Exiting."; exit 1; 
 887         cmd_compress
="tee /dev/null " && vbm 
"STATUS:$fn:cmd_compress:$cmd_compress"; 
 888         cmd_compress_suffix
="" && vbm 
"STATUS:$fn:cmd_compress_suffix:$cmd_compress_suffix"; 
 889         vbm 
"DEBUG :$fn:Compression not enabled."; 
 891     vbm 
"STATUS:$fn:Finished magicParseCompressionArg() function."; 
 892 } # Form compression cmd string and filename suffix 
 893 magicParseCustomTTL
() { 
 894     # Desc: Set user-specified TTLs for buffer and script 
 895     # Usage: magicParseCustomTTL 
 896     # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string) 
 897     # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE 
 898     # Input: vars: bufferTTL (integer), scriptTTL_TE (string) 
 899     # Output: bufferTTL (integer), scriptTTL_TE (string) 
 900     # Depends: Bash 5.0.3, yell(), vbm(), validateInput(), showUsage() 
 906     vbm 
"STATUS:$fn:Starting magicParseCustomTTL() function."; 
 907     # React to '-b, --buffer-ttl' option 
 908     if [[ "$optionCustomBufferTTL" = "true" ]]; then 
 909         ## T: Check if argCustomBufferTTL is an integer 
 910         if validateInput 
"$argCustomBufferTTL" "integer"; then 
 911             ### T: argCustomBufferTTL is an integer 
 912             bufferTTL
="$argCustomBufferTTL" && vbm 
"STATUS:$fn:Custom bufferTTL from -b:$bufferTTL"; 
 914             ### F: argcustomBufferTTL is not an integer 
 915             yell 
"ERROR:$fn:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1; 
 917         ## F: do not change bufferTTL 
 920     # React to '-B, --script-ttl' option 
 921     if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then 
 922         ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour") 
 923         if validateInput 
"$argCustomScriptTTL_TE" "time_element"; then 
 924             ### T: argCustomScriptTTL is a time element 
 925             scriptTTL_TE
="$argCustomScriptTTL_TE" && vbm 
"STATUS:$fn:Custom scriptTTL_TE from -B:$scriptTTL_TE"; 
 927             ### F: argcustomScriptTTL is not a time element 
 928             yell 
"ERROR:$fn:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1; 
 930         ## F: do not change scriptTTL_TE 
 932     vbm 
"STATUS:$fn:Finished magicParseCustomTTL() function."; 
 933 } # Sets custom script or buffer TTL if specified 
 935     # Desc: Parses -l option to set label 
 936     # In : optionLabel, argLabel 
 938     # Depends: Bash 5.0.3, vbm(), yell() 
 944     vbm 
"STATUS:$fn:Started magicParseLabel() function."; 
 945     # Do nothing if optionLabel not set to true. 
 946     if [[ ! "$optionLabel" = "true" ]]; then 
 947         vbm 
"STATUS:$fn:optionlabel not set to 'true'. Returning early."; 
 950     # Set label if optionLabel is true 
 951     if [[ "$optionLabel" = "true" ]]; then 
 952         label
="_""$argLabel"; 
 953         vbm 
"STATUS:$fn:Set label:$label"; 
 955     vbm 
"STATUS:$fn:Finished magicParseLabel() function."; 
 956 } # Set label used in output file name 
 957 magicParseProcessStrings
() { 
 958     # Desc: Processes user-supplied process strings into process commands for appendFileTar(). 
 959     # Usage: magicParseProcessStrings 
 960     # In : vars: optionProcString optionNoStoreRaw optionStoreRaw argRawFileExt 
 961     #      arry: argProcStrings, argProcFileExts 
 962     # Out: arry: procStrings, procFileExts 
 963     # Depends Bash 5.0.3, yell(), vbm() 
 969     vbm 
"STATUS:$fn:Starting magicParseProcessStrings() function."; 
 970     vbm 
"STATUS:$fn:var:optionProcString:$optionProcString"; 
 971     vbm 
"STATUS:$fn:var:optionNoStoreRaw:$optionNoStoreRaw"; 
 972     vbm 
"STATUS:$fn:var:optionStoreRaw:$optionStoreRaw"; 
 973     vbm 
"STATUS:$fn:var:argRawFileExt:$argRawFileExt"; 
 974     vbm 
"STATUS:$fn:ary:argProcStrings:${argProcStrings[*]}"; 
 975     vbm 
"STATUS:$fn:ary:argProcFileExts:${argProcFileExts[*]}" 
 977     ## Validate argRawFileExt 
 978     if [[ "$argRawFileExt" =~ ^
[.
][[:alnum
:]]*$ 
]]; then 
 979         rawFileExt
="$argRawFileExt" && \
 
 980             vbm 
"DEBUG :$fn:Set rawFileExt to \"$argRawFileExt\""; 
 982         vbm 
"DEBUG :$fn:Validation failure for $argRawFileExt . Not set to rawFileExt."; 
 985     # Add default stdin output file entries for procStrings, procFileExts 
 986     ## Check if user specified that no raw stdin be saved. 
 987     if [[ ! "$optionNoStoreRaw" = "true" ]]; then 
 988         ### T: --no-store-raw not set. Store raw. Append procStrings with cat. 
 989         vbm 
"DEBUG :$fn:--no-store-raw not set. Storing raw."; 
 990         #### Append procStrings array 
 991         procStrings
+=("cat ") && \
 
 992             vbm 
"DEBUG :$fn:Appended \"cat \" to procStrings"; 
 993         vbm 
"DEBUG :$fn:procStrings array:${procStrings[*]}"; 
 994         #### Check if --store-raw set. 
 995         if [[ "$optionStoreRaw" = "true" ]]; then 
 996             ##### T: --store-raw set. Append procFileExts with user-specified file ext 
 997             vbm 
"DEBUG :$fn:--store-raw set."; 
 998             procFileExts
+=("$rawFileExt") && \
 
 999                 vbm 
"DEBUG :$fn:Appended $rawFileExt to procFileExts"; 
1000             vbm 
"STATUS:$fn:procFileExts array:${procFileExts[*]}"; 
1002             ##### F: --store-raw not set. Append procFileExts with default ".stdin" file ext 
1003             ###### Append procFileExts array 
1004             procFileExts
+=(".stdin") && \
 
1005                 vbm 
"DEBUG :$fn:Appended \".stdin\" to procFileExts"; 
1006             vbm 
"STATUS:$fn:procFileExts array:${procFileExts[*]}"; 
1009         ### F: --no-store-raw set. Do not store raw. 
1010         #### Do not append procStrings or procFileExts arrays. 
1011         vbm 
"STATUS:$fn:--no-store-raw set. Not storing raw."; 
1012         vbm 
"STATUS:$fn:procFileExts array:${procFileExts[*]}"; 
1015     # Do nothing more if optionProcString not set to true. 
1016     if [[ ! "$optionProcString" = "true" ]]; then 
1017         vbm 
"STATUS:$fn:optionProcString not set to 'true'. Returning early."; 
1019     # Validate input array indices 
1020     ## Make sure that argProcStrings and argProcFileExts have same index counts 
1021     if ! [[ "${#argProcStrings[@]}" -eq "${#argProcFileExts[@]}" ]]; then 
1022         yell 
"ERROR:$fn:Mismatch in number of elements in arrays argProcStrings and argProcFileExts:${#argProcStrings[@]} DNE ${#argProcFileExts[@]}"; 
1023         yell 
"STATUS:$fn:argProcStrings:${argProcStrings[*]}"; yell 
"STATUS:$fn:argProcFileExts:${argProcFileExts[*]}"; exit 1; fi; 
1024     ## Make sure that no array elements are blank 
1025     for element 
in "${argProcStrings[@]}"; do 
1026         if [[ -z "$element" ]]; then yell 
"ERROR:$fn:Empty process string specified. Exiting."; exit 1; fi; done 
1027     for element 
in "${argProcFileExts[@]}"; do 
1028         if [[ -z "$element" ]]; then yell 
"ERROR:$fn:Empty output file extension specified. Exiting."; exit 1; fi; done 
1029     ## Make sure that no process string starts with '-' (ex: if only one arg supplied after '-p' option) 
1030     for element 
in "${argProcStrings[@]}"; do 
1031         if [[ "$element" =~ ^
[-][[:print
:]]*$ 
]] && [[ ! "$element" =~ ^
[[:print
:]]*$ 
]]; then 
1032             yell 
"ERROR:$fn:Illegal character '-' at start of process string element:\"$element\""; 
1034     vbm 
"STATUS:$fn:Quick check shows argProcStrings and argProcFileExts appear to have valid contents."; 
1035     vbm 
"STATUS:$fn:argProcStrings:${argProcStrings[*]}" 
1036     vbm 
"STATUS:$fn:argProcFileExts:${argProcFileExts[*]}" 
1037     procStrings
+=("${argProcStrings[@]}"); # Export process command strings 
1038     procFileExts
+=("${argProcFileExts[@]}"); # Export process command strings 
1039     vbm 
"STATUS:$fn:procStrings:${procStrings[*]}" 
1040     vbm 
"STATUS:$fn:procFileExts:${procFileExts[*]}" 
1041     vbm 
"STATUS:$fn:Finished magicParseProcessStrings() function."; 
1042 } # Validate and save process strings and file extensions to arrays procStrings, procFileExts 
1043 magicParseRecipients
() { 
1044     # Desc: Parses recipient arguments specified by '-r' or '-R' options 
1045     # Usage: magicParseRecipients 
1046     # In : vars: optionEncrypt, optionRecArg, optionRecDir 
1047     #      arry: argRecPubKeys (-r), argRecDir (-R) 
1048     # Out: vars: cmd_encrypt, cmd_encrypt_suffix 
1049     # Depends: head 8.30, checkapp(), checkAgePubkey(), validateInput() 
1050     local fn recipients recipientDir recFileLine updateRecipients
 
1051     local -a recPubKeysValid candRecPubKeysValid
 
1053     # Save function name 
1054     fn
="${FUNCNAME[0]}"; 
1055     vbm 
"STATUS:$fn:Starting magicParseRecipients() function."; 
1057     # Catch illegal option combinations 
1058     ## Catch case if '-e' is set but neither '-r' nor '-R' is set 
1059     if [[ "$optionEncrypt" = "true" ]] && \
 
1060            ! { [[ "$optionRecArg" = "true" ]] || 
[[ "$optionRecDir" = "true" ]]; }; then 
1061         yell 
"ERROR:$fn:\\'-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" ]] && \
 
1064            { [[ "$optionRecArg" = "true" ]] || 
[[ "$optionRecDir" = "true" ]]; }; then 
1065         yell 
"ERROR:$fn:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi; 
1067     # Handle no encryption cases 
1068     if [[ ! "$optionEncrypt" = "true" ]]; then 
1069         cmd_encrypt
="cat " && vbm 
"STATUS:$fn:cmd_encrypt:$cmd_encrypt"; 
1070         cmd_encrypt_suffix
="" && vbm 
"STATUS:$fn:cmd_encrypt_suffix:$cmd_encrypt_suffix"; 
1071         vbm 
"DEBUG :$fn:Encryption not enabled."; 
1074     # Handle encryption cases 
1075     ## Check age availability 
1076     if ! checkapp age
; then yell 
"ERROR:$fn:age not available. Exiting."; exit 1; fi 
1077     ## Parse '-r' options: validate and append pubkeys from argRecPubKeys to recPubKeysValid 
1078     if [[ "$optionRecArg" = "true" ]]; then 
1079         for pubkey 
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message 
1080             vbm 
"DEBUG :$fn:Testing pubkey string:$pubkey"; 
1081             if checkAgePubkey 
"$pubkey" && \
 
1082                     ( validateInput 
"$pubkey" "ssh_pubkey" || validateInput 
"$pubkey" "age_pubkey"); then 
1083                 #### Add validated pubkey to recPubKeysValid array 
1084                 recPubKeysValid
+=("$pubkey") && \
 
1085                     vbm 
"DEBUG :$fn:recPubkeysValid:pubkey added:$pubkey"; 
1087                 yell 
"ERROR:$fn:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1; 
1090         vbm 
"STATUS:$fn:Finished processing argRecPubKeys array"; 
1091         vbm 
"DEBUG :$fn:Array of validated pubkeys:${recPubKeysValid[*]}"; 
1093     ## Parse '-R' options: validate and append pubkeys in argRecDir to recPubKeysValid 
1094     if [[ "$optionRecDir" = "true" ]]; then 
1095         ### Check that argRecDir is a directory 
1096         if [[ -d "$argRecDir" ]]; then 
1097             recipientDir
="$argRecDir" && \
 
1098                 vbm 
"STATUS:$fn:Recipient watch directory detected:\"$recipientDir\""; 
1099             #### Initialize variable indicating outcome of pubkey review 
1100             unset updateRecipients
 
1101             #### Add existing recipients from '-r' option 
1102             candRecPubKeysValid
=("${recPubKeysValid[@]}"); 
1103             #### Parse files in recipientDir 
1104             for file in "$recipientDir"/*; do 
1105                 ##### Read first line of each file 
1106                 recFileLine
="$(head -n1 "$file")" && \
 
1107                     vbm 
"STATUS:$fn:Checking if pubkey:\"$recFileLine\""; 
1108                 ##### check if first line is a valid pubkey 
1109                 if checkAgePubkey 
"$recFileLine" && \
 
1110                         ( validateInput 
"$recFileLine" "ssh_pubkey" || validateInput 
"$recFileLine" "age_pubkey"); then 
1111                     ###### T: add candidate pubkey to candRecPubKeysValid 
1112                     candRecPubKeysValid
+=("$recFileLine") && \
 
1113                         vbm 
"STATUS:$fn:RecDir pubkey is valid pubkey:\"$recFileLine\""; 
1115                     ###### F: throw warning; 
1116                     yell 
"ERROR:$fn:Invalid recipient file detected. Not modifying recipient list:$recFileLine"; 
1117                     updateRecipients
="false"; 
1120             #### Write candRecPubKeysValid array to recPubKeysValid if no invalid key detected 
1121             if ! [[ "$updateRecipients" = "false" ]]; then 
1122                 recPubKeysValid
=("${candRecPubKeysValid[@]}") && \
 
1123                     vbm 
"STATUS:$fn:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\""; 
1128     ## Form age recipient string from recPubKeysValid 
1129     for pubkey 
in "${recPubKeysValid[@]}"; do 
1130         recipients
="$recipients""-r '$pubkey' "; 
1131         vbm 
"STATUS:$fn:Added pubkey for forming age recipient string:""$pubkey"; 
1132         vbm 
"DEBUG :$fn:recipients:""$recipients";       
1135     ## Output cmd_encrypt, cmd_encrypt_suffix from recipients 
1136     cmd_encrypt
="age ""$recipients " && vbm 
"STATUS:$fn:cmd_encrypt:$cmd_encrypt"; 
1137     cmd_encrypt_suffix
=".age" && vbm 
"STATUS:$fn:cmd_encrypt_suffix:$cmd_encrypt_suffix"; 
1139     vbm 
"STATUS:$fn:Finished magicParseRecipients() function."; 
1140 } # Sets cmd_encrypt, cmd_encrypt_suffix from -r, -R args 
1141 magicSetScriptTTL
() { 
1142     #Desc: Sets script_TTL seconds from provided time_element string argument 
1143     #Usage: magicSetScriptTTL [str time_element] 
1144     #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour") 
1145     #Output: var: scriptTTL (integer seconds) 
1146     #Depends: timeUntilNextHour, timeUntilNextDay 
1147     local fn argTimeElement
 
1149     # Save function name 
1150     fn
="${FUNCNAME[0]}"; 
1152     vbm 
"STATUS:$fn:Starting magicSetScriptTTL() function."; 
1153     argTimeElement
="$1"; 
1154     if [[ "$argTimeElement" = "day" ]]; then 
1155         # Set script lifespan to end at start of next day 
1156         vbm 
"STATUS:$fn:Setting script lifespan to end at start of next day. argTimeElement:$argTimeElement"; 
1157         if ! scriptTTL
="$(timeUntilNextDay)"; then # sets scriptTTL, then checks exit code 
1158             if [[ "$scriptTTL" -eq 0 ]]; then 
1159                 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
1160                 vbm 
"STATUS:$fn:scriptTTL:$scriptTTL"; 
1162             yell 
"ERROR:$fn:timeUntilNextDay exit code $?"; exit 1; 
1165     elif [[ "$argTimeElement" = "hour" ]]; then 
1166         # Set script lifespan to end at start of next hour 
1167         vbm 
"STATUS:$fn:Setting script lifespan to end at start of next hour. argTimeElement:$argTimeElement"; 
1168         if ! scriptTTL
="$(timeUntilNextHour)"; then # sets scriptTTL, then checks exit code 
1169             if [[ "$scriptTTL" -eq 0 ]]; then 
1170                 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
1171                 vbm 
"STATUS:$fn:scriptTTL:$scriptTTL"; 
1173                 yell 
"ERROR:$fn:timeUntilNextHour exit code $?"; exit 1; 
1177         yell 
"ERROR:$fn:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1; 
1179     vbm 
"STATUS:$fn:Finished magicSetScriptTTL() function."; 
1180 } # Set scriptTTL in seconds until next (day|hour). 
1181 magicVerboseReadout
() { 
1182     vbm 
"$bufferTTL"; # Time-to-live (seconds) for each buffer round 
1183     vbm 
"$scriptTTL_TE"; # Time element at the end of which script terminates 
1184     vbm 
"$dirTmpDefault"; # Default parent of working directory 
1186     vbm 
"$scriptName"; # Basename of script file. 
1187     vbm 
"$scriptVersion"; # Version of script. 
1188     vbm 
"$scriptURL"; # Website hosting this script. 
1189     vbm 
"$scriptTimeStartEpoch"; # Start time of script in epoch seconds 
1190     vbm 
"$scriptTimeStart"; # YYYYmmddTHHMMSS.NNNNNNNNN 
1191     vbm 
"$scriptHostname" # Hostname of system running this script. 
1192     vbm 
"$PATH"; # PATH env. var. 
1193     vbm 
"$ageVersion"; # Version of age (encryption program) 
1194     vbm 
"$ageURL"; # Website hosting age. 
1195 } # Display script variables 
1196 magicWriteVersion
() { 
1197     # Desc: Appends time-stamped VERSION to pathout_tar 
1198     # Usage: magicWriteVersion 
1199     # Input: vars: pathout_tar, dir_tmp 
1200     # Input: vars: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname 
1201     # Input: array: recPubKeysValid 
1202     # Output: appends tar (pathout_tar) 
1203     # Depends: bash 5.0.3, dateTimeShort(), appendArgTar() 
1204     local fn fileoutVersion contentVersion pubKeyIndex pubKeyIndex
 
1206     # Save function name 
1207     fn
="${FUNCNAME[0]}"; 
1209     vbm 
"STATUS:$fn:Starting magicWriteVersion() function."; 
1210     # Set VERSION file name 
1211     fileoutVersion
="$(dateTimeShort)..VERSION"; 
1213     # Gather VERSION data in contentVersion 
1214     contentVersion
="scriptVersion=$scriptVersion"; 
1215     #contentVersion="$contentVersion""\\n"; 
1216     contentVersion
="$contentVersion""\\n""scriptName=$scriptName"; 
1217     contentVersion
="$contentVersion""\\n""scriptURL=$scriptURL"; 
1218     contentVersion
="$contentVersion""\\n""ageVersion=$ageVersion"; 
1219     contentVersion
="$contentVersion""\\n""ageURL=$ageURL"; 
1220     contentVersion
="$contentVersion""\\n""date=$(date --iso-8601=seconds)"; 
1221     contentVersion
="$contentVersion""\\n""hostname=$scriptHostname"; 
1222     ## Add list of recipient pubkeys 
1223     for pubkey 
in "${recPubKeysValid[@]}"; do 
1225         contentVersion
="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey"; 
1227     ## Process newline escapes 
1228     contentVersion
="$(echo -e "$contentVersion")" 
1230     # Write contentVersion as file fileoutVersion and write-append to pathout_tar 
1231     appendArgTar 
"$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp" && \
 
1232         vbm 
"STATUS:$fn:Appended $fileoutVersion to $pathout_tar"; 
1233     vbm 
"STATUS:$fn:Finished magicWriteVersion() function."; 
1234 } # write version data to pathout_tar via appendArgTar() 
1235 magicProcessWriteBuffer
() { 
1236     # Desc: process and write buffer 
1237     # In : vars: bufferTTL scriptHostname label dir_tmp SECONDS 
1238     #    : vars: timeBufferStartEpoch timeBufferEndEpoch 
1240     # Out: file:(pathout_tar) 
1241     # Depends: Bash 5.0.3, date 8.30, yell(), vbm(), dateTimeShort(), 
1242     ### Note: These arrays should all have the same number of elements: 
1243     ###       pathouts, fileouts, procFileExts, procStrings 
1245     local fn timeBufferStartLong timeBufferStart bufferDuration bufferDurationStr fileoutBasename
 
1246     local -a fileouts pathouts
 
1247     local writeCmd1 writeCmd2 writeCmd3 writeCmd4
 
1249     # Debug:Get function name 
1250     fn
="${FUNCNAME[0]}"; 
1252     vbm 
"STATUS:$fn:Started magicProcessWriteBuffer()."; 
1253     vbm 
"DEBUG :$fn:buffer array element count:${#buffer[@]}"; 
1254     vbm 
"DEBUG :$fn:buffer array first element:${buffer[0]}"; 
1255     vbm 
"DEBUG :$fn:buffer array last element :${buffer[-1]}"; 
1257     # Determine file paths (time is start of buffer period) 
1258     ## Calculate start time 
1259     timeBufferStartLong
="$(date --date="@
$timeBufferStartEpoch" --iso-8601=seconds)" && \
 
1260         vbm 
"DEBUG :$fn:timeBufferStartLong:$timeBufferStartLong"; # Note start time in 'date' parsable ISO-8601 
1261     timeBufferStart
="$(dateTimeShort "$timeBufferStartLong" )" && \
 
1262         vbm 
"DEBUG :$fn:timeBufferStart:$timeBufferStart"; # Note start time YYYYmmddTHHMMSS+zzzz (no separators) 
1263     ## Calculate buffer duration string (ISO-8601 duration) 
1264     bufferDuration
="$((timeBufferEndEpoch - timeBufferStartEpoch))" && \
 
1265         vbm 
"DEBUG :$fn:bufferDuration:$bufferDuration"; # length of time (seconds) stdin was read 
1266     bufferDurationStr
="$(timeDuration "$bufferDuration")" && \
 
1267         vbm 
"DEBUG :$fn:bufferDurationStr:$bufferDurationStr"; # buffer duration (ISO-8601) 
1268     ## Set common basename 
1269     fileoutBasename
="$timeBufferStart""--""$bufferDurationStr""..""$scriptHostname""$label" && \
 
1270         vbm 
"STATUS:$fn:Set fileoutBasename to:$fileoutBasename"; 
1271     ## Determine output file name array 
1272     ### in: fileOutBasename cmd_compress_suffix cmd_encrypt_suffix procFileExts 
1273     for fileExt 
in "${procFileExts[@]}"; do 
1274         fileouts
+=("$fileoutBasename""$fileExt""$cmd_compress_suffix""$cmd_encrypt_suffix") && \
 
1275             vbm 
"STATUS:$fn:Added $fileExt to fileouts:${fileouts[*]}"; 
1277     for fileName 
in "${fileouts[@]}"; do 
1278         pathouts
+=("$dir_tmp"/"$fileName") && \
 
1279             vbm 
"STATUS:$fn:Added $fileName to pathouts:${pathouts[*]}"; 
1281     ## Update pathout_tar 
1284     # Process and write buffers to dir_tmp 
1285     ## Prepare command strings 
1286     writeCmd1
="printf \"%s\\\\n\" \"\${buffer[@]}\""; # printf "%s\\n" "${buffer[@]}" 
1287     #writeCmd2="" # NOTE: Specified by parsing array procStrings 
1288     writeCmd3
="$cmd_compress"; 
1289     writeCmd4
="$cmd_encrypt"; 
1291     ## Process buffer and write to dir_tmp 
1292     vbm 
"DEBUG :$fn:fileouts    element count:${#fileouts[@]}"; 
1293     vbm 
"DEBUG :$fn:pathouts    element count:${#pathouts[@]}"; 
1294     vbm 
"DEBUG :$fn:procStrings element count:${#pathouts[@]}"; 
1295     vbm 
"DEBUG :$fn:fileouts    contents:${fileouts[*]}"; 
1296     vbm 
"DEBUG :$fn:pathouts    contents:${pathouts[*]}"; 
1297     vbm 
"DEBUG :$fn:procStrings contents:${pathouts[*]}"; 
1298     for index 
in "${!pathouts[@]}"; do 
1299         writeCmd2
="${procStrings[$index]}"; 
1300         writeCmdAll
="$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" && vbm 
"STATUS:$fn:Assembled command:\"$writeCmdAll\""; 
1301         eval "$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" > "${pathouts[$index]}" && vbm 
"STATUS:$fn:Wrote command output to ${pathouts[$index]}"; 
1304     # Append dir_tmp files to pathout_tar 
1305     wait; # Wait to avoid collision with older magicProcessWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) 
1306     for index 
in "${!pathouts[@]}"; do 
1307         tar --append --directory="$dir_tmp" --file="$pathout_tar" "${fileouts[$index]}" && \
 
1308             vbm 
"STATUS:$fn:Appended ${pathouts[$index]} to $pathout_tar"; 
1309         #appendFileTar "${pathouts[$index]}" "${fileouts[$index]}" "$pathout_tar" "$dir_tmp" && \ 
1312     # Remove secured chunks from dir_tmp 
1313     for path 
in "${pathouts[@]}"; do 
1314         rm "$path" && vbm 
"STATUS:$fn:Removed:$path"; 
1317     vbm 
"STATUS:$fn:Finished magicProcessWriteBuffer()."; 
1318 } # Process and Write buffer 
1322         cmd | bklog [ options ] 
1326                 Display help information. 
1328                 Display script version. 
1330                 Display debugging info. 
1333         -r, --recipient [ string pubkey ] 
1334                 Specify recipient. May be age or ssh pubkey. 
1335                 May be specified multiple times for multiple pubkeys. 
1336                 See https://github.com/FiloSottile/age 
1337         -o, --output [ path dir ] 
1338                 Specify output directory to save logs. This option is required 
1340         -p, --process-string [ filter command ] [ output file extension]  
1341                 Specify how to create and name a processed version of the stdin. 
1342                 For example, if stdin is 'nmea' location data: 
1344                 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" 
1346                 This option would cause the stdin to 'bklog' to be piped into 
1347                 the 'gpsbabel' command, interpreted as 'nmea' data, converted 
1348                 into 'gpx' format, and then appended to the output tar file 
1349                 as a file with a '.gpx' extension. 
1350                 This option may be specified multiple times in order to output 
1351                 results of multiple different processing methods. 
1352         -l, --label [ string ] 
1353                 Specify a label to be included in all output file names. 
1354                 Ex: 'location' if stdin is location data. 
1355         -w, --store-raw [ file extension ] 
1356                 Specify file extension of file within output tar that contains 
1357                 raw stdin data. The default behavior is to always save raw stdin 
1358                 data in a '.stdin' file. Example usage when 'bklog' receives 
1359                 'nmea' data from 'gpspipe -r': 
1363                 Stdin data is saved in a '.nmea' file within the output tar. 
1365                 Do not store raw stdin in output tar. 
1367                 Compress output with gzip (before encryption if enabled). 
1369                 Specify time zone. (ex: "America/New_York") 
1370         -t, --temp-dir [path dir] 
1371                 Specify parent directory for temporary working directory. 
1373         -R, --recipient-dir [path dir] 
1374                 Specify directory containing files whose first lines are 
1375                 to be interpreted as pubkey strings (see '-r' option). Only 
1376                 one directory may be specified. 
1377         -b, --buffer-ttl [integer] 
1378                 Specify custom buffer period in seconds (default: 300 seconds) 
1379         -B, --script-ttl [time element string] 
1380                 Specify custom script time-to-live in seconds (default: "day") 
1381                 Valid values: "day", "hour" 
1383     EXAMPLE: (bash script lines) 
1384     $ gpspipe -r | /bin/bash bklog -v -e -c -z "UTC" -t "/dev/shm" \ 
1385     -r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \ 
1386     -r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \ 
1387     -R ~/.config/bklog/recipients -w ".nmea" -b 300 -B "day" \ 
1388     -o ~/Sync/Logs -l "location" \ 
1389     -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" \ 
1390     -p "gpsbabel -i nmea -f - -o kml -F - " ".kml" 
1392 } # Display information on how to use this script. 
1395     # Desc: Main function 
1398     # Outputs: file (pathout_tar) 
1402     # Debug:Get function name 
1403     fn
="${FUNCNAME[0]}"; 
1405     vbm 
"STATUS:$fn:Started function main()."; 
1407     processArguments 
"$@"; 
1408     ## Determine working directory 
1409     magicInitWorkingDir
; # Sets dir_tmp from argTempDirPriority 
1410     ## Set output encryption and compression option strings 
1411     ### React to "-e", "-r", and "-R" (encryption recipients) options 
1412     magicParseRecipients
; # Update cmd_encrypt, cmd_encrypt_suffix 
1413     ### React to "-c" ("compression") option 
1414     magicParseCompressionArg
; # Updates cmd_compress[_suffix] 
1415     ## React to "-b" and "-B" (custom buffer and script TTL) options 
1416     magicParseCustomTTL
; # Sets custom scriptTTL_TE and/or bufferTTL if specified 
1417     ## React to "-p" (user-supplied process command and file extension strings) options 
1418     magicParseProcessStrings
; # Sets arrays: procStrings, procFileExts 
1419     ## React to "-l" (output file label) option 
1420     magicParseLabel
; # sets label (ex: "_location") 
1421     ## React to "-v" (verbose) option 
1422     magicVerboseReadout
; # Display various script variables 
1424     # Perform secondary setup operations 
1425     ## Set script lifespan (scriptTTL from scriptTTL_TE) 
1426     magicSetScriptTTL 
"$scriptTTL_TE"; 
1427     ## Adjust SECONDS so buffer rounds align with time elements 
1428     ### Advance SECONDS the remainder seconds for dividend timeUntilNextDay, divisor bufferTTL 
1429     if [[ "$(timeUntilNextDay)" -gt "$bufferTTL" ]]; then 
1430         vbm 
"DEBUG :$fn:SECONDS currently  :$SECONDS"; 
1431         SECONDS
="$(( bufferTTL - ($(timeUntilNextDay) % bufferTTL) ))" && \
 
1432             vbm 
"DEBUG :$fn:SECONDS advanced to:$SECONDS"; 
1433         vbm 
"DEBUG :$fn:current time:$(date --iso-8601=seconds)"; 
1435     ## Init temp working dir 
1436     try mkdir 
"$dir_tmp" && vbm 
"DEBUG :$fn:Working dir created at dir_tmp:$dir_tmp"; 
1437     ## Initialize output tar (set pathout_tar) 
1439     ## Append VERSION file to tar 
1442     # Check vital apps, files, dirs 
1443     if ! checkapp 
tar && ! checkdir 
"$dirOut" "dir_tmp"; then 
1444         yell 
"ERROR:$fn:Critical components missing."; 
1445         displayMissing
; yell 
"Exiting."; exit 1; fi 
1447     # MAIN LOOP: Run until script TTL seconds pass 
1449     while [[ $SECONDS -lt "scriptTTL" ]]; do 
1450         vbm 
"STATUS:$fn:Starting buffer round:$bufferRound"; 
1451         bufferTOD
="$(( (1+bufferRound)*bufferTTL ))" && vbm 
"DEBUG :$fn:bufferTOD:$bufferTOD"; # Set buffer round time-of-death 
1452         # Consume stdin to fill buffer until buffer time-of-death (TOD) arrives 
1453         while read -r -t "$bufferTTL" line 
&& [[ $SECONDS -lt "$bufferTOD" ]]; do 
1454             # Append line to buffer array 
1457         # Mark time for buffer 
1459         if [[ bufferRound 
-gt 0 ]]; then 
1461             timeBufferStartEpoch
="$timeBufferEndEpoch" && vbm 
"DEBUG :$fn:timeBufferStartEpoch:$timeBufferStartEpoch"; 
1462         elif [[ bufferRound 
-eq 0 ]]; then 
1463             ### Edge case: initial startup 
1464             timeBufferStartEpoch
="$scriptTimeStartEpoch" && vbm 
"DEBUG :$fn:timeBufferStartEpoch:$timeBufferStartEpoch"; 
1466             yell 
"ERROR:$fn:Invalid bufferRound value."; exit 1; 
1469         timeBufferEndEpoch
="$(date +%s)" && vbm 
"DEBUG :$fn:timeBufferEndEpoch:$timeBufferEndEpoch"; 
1470         # Create dir_tmp if missing 
1471         if ! [[ -d "$dir_tmp" ]]; then 
1472             yell 
"ERROR:$fn:dir_tmp existence failure:$dir_tmp"; 
1473             try mkdir 
"$dir_tmp" && vbm 
"DEBUG :$fn:Working dir recreated dir_tmp:$dir_tmp"; fi 
1474         # Update cmd_encrypt, cmd_encrypt_suffix 
1475         magicParseRecipients
; 
1476         # Export buffer to asynchronous processing. 
1477         magicProcessWriteBuffer 
& 
1478         unset buffer
; # Clear buffer array for next bufferRound 
1479         # Increment buffer round 
1485     try 
rm -r "$dir_tmp" && vbm 
"STATUS:$fn:Removed dir_tmp:$dir_tmp"; 
1487     vbm 
"STATUS:$fn:Finished function main()."; 
1490 #===END Declare local script functions=== 
1491 #==END Define script parameters== 
1493 #==BEGIN Perform work and exit== 
1494 main 
"$@" # Run main function. 
1496 #==END Perform work and exit== 
1498 # Author: Steven Baltakatei Sandoval;