2 # Desc: Records gps data 
   4 #==BEGIN Define script parameters== 
   5 ## Logging Behavior parameters 
   6 BUFFER_TTL
="300"; # time between file writes 
   7 SCRIPT_TTL_TE
="day"; # (day|hour) 
   8 #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option 
   9 DIR_TMP_DEFAULT
="/dev/shm"; # Default parent of working directory 
  11 SCRIPT_TIME_START
=$(date +%Y%m%dT%H%M%S.%N); 
  12 PATH
="$HOME/.local/bin:$PATH";   # Add "$(systemd-path user-binaries)" path in case apps saved there 
  13 SCRIPT_HOSTNAME
=$(hostname);     # Save hostname of system running this script. 
  14 SCRIPT_VERSION
="0.5.6";          # Define version of script. 
  15 SCRIPT_NAME
="bkgpslog";          # Define basename of script file. 
  16 SCRIPT_URL
="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script. 
  17 AGE_VERSION
="1.0.0-beta2";       # Define version of age (encryption program) 
  18 AGE_URL
="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age. 
  20 declare -Ag appRollCall 
# Associative array for storing app status 
  21 declare -Ag fileRollCall 
# Associative array for storing file status 
  22 declare -Ag dirRollCall 
# Associative array for storing dir status 
  23 declare -a argRecPubKeys 
# for processArguments function 
  24 # declare -a errorHistory # for correcting buffer lag 
  26 ## Initialize variables 
  27 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
=""; 
  28 errReset
=0; BUFFER_TTL_ADJ_FLOAT
=""; 
  30 #===BEGIN Declare local script functions=== 
  32     # Desc: If arg is a command, save result in assoc array 'appRollCall' 
  33     # Usage: checkapp arg1 arg2 arg3 ... 
  34     # Input: global assoc. array 'appRollCall' 
  35     # Output: adds/updates key(value) to global assoc array 'appRollCall' 
  37     #echo "DEBUG:$(date +%S.%N)..Starting checkapp function." 
  38     #echo "DEBUG:args: $@" 
  39     #echo "DEBUG:returnState:$returnState" 
  43         #echo "DEBUG:processing arg:$arg" 
  44         if command -v "$arg" 1>/dev
/null 
2>&1; then # Check if arg is a valid command 
  45             appRollCall
[$arg]="true"; 
  46             #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]} 
  47             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  49             appRollCall
[$arg]="false"; returnState
="false"; 
  53     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
  54     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
  56     #===Determine function return code=== 
  57     if [ "$returnState" = "true" ]; then 
  58         #echo "DEBUG:checkapp returns true for $arg"; 
  61         #echo "DEBUG:checkapp returns false for $arg"; 
  64 } # Check that app exists 
  66     # Desc: If arg is a file path, save result in assoc array 'fileRollCall' 
  67     # Usage: checkfile arg1 arg2 arg3 ... 
  68     # Input: global assoc. array 'fileRollCall' 
  69     # Output: adds/updates key(value) to global assoc array 'fileRollCall'; 
  70     # Output: returns 0 if app found, 1 otherwise 
  75         #echo "DEBUG:processing arg:$arg" 
  76         if [ -f "$arg" ]; then 
  77             fileRollCall
["$arg"]="true"; 
  78             #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]} 
  79             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  81             fileRollCall
["$arg"]="false"; returnState
="false"; 
  85     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done 
  86     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
  88     #===Determine function return code=== 
  89     if [ "$returnState" = "true" ]; then 
  90         #echo "DEBUG:checkapp returns true for $arg"; 
  93         #echo "DEBUG:checkapp returns false for $arg"; 
  96 } # Check that file exists 
  98     # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' 
  99     # Usage: checkdir arg1 arg2 arg3 ... 
 100     # Input: global assoc. array 'dirRollCall' 
 101     # Output: adds/updates key(value) to global assoc array 'dirRollCall'; 
 102     # Output: returns 0 if app found, 1 otherwise 
 107         #echo "DEBUG:processing arg:$arg" 
 108         if [ -d "$arg" ]; then 
 109             dirRollCall
["$arg"]="true"; 
 110             #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]} 
 111             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
 112         elif [ "$arg" = "" ]; then 
 113             dirRollCall
["$arg"]="false"; returnState
="false"; 
 119     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done 
 120     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
 122     #===Determine function return code=== 
 123     if [ "$returnState" = "true" ]; then 
 124         #echo "DEBUG:checkapp returns true for $arg"; 
 127         #echo "DEBUG:checkapp returns false for $arg"; 
 130 } # Check that dir exists 
 132 # Yell, Die, Try Three-Fingered Claw technique 
 133 # Ref/Attrib: https://stackoverflow.com/a/25515370 
 134 yell
() { echo "$0: $*" >&2; } 
 135 die
() { yell 
"$*"; exit 111; } 
 136 try
() { "$@" || die 
"cannot $*"; } 
 139     echo "$@" 1>&2; # Define stderr echo function. 
 140 } # Define stderr message function. 
 143     echoerr 
"    bkgpslog [ options ]" 
 146     echoerr 
"    -h, --help" 
 147     echoerr 
"            Display help information." 
 149     echoerr 
"            Display script version." 
 150     echoerr 
"    -v, --verbose" 
 151     echoerr 
"            Display debugging info." 
 152     echoerr 
"    -e, --encrypt" 
 153     echoerr 
"            Encrypt output." 
 154     echoerr 
"    -r, --recipient [ string pubkey ]" 
 155     echoerr 
"            Specify recipient. May be age or ssh pubkey." 
 156     echoerr 
"            May be specified multiple times for multiple pubkeys." 
 157     echoerr 
"            See https://github.com/FiloSottile/age" 
 158     echoerr 
"    -o, --output [ path dir ]" 
 159     echoerr 
"            Specify output directory to save logs." 
 160     echoerr 
"    -c, --compress" 
 161     echoerr 
"            Compress output with gzip (before encryption if enabled)." 
 162     echoerr 
"    -z, --time-zone" 
 163     echoerr 
"            Specify time zone. (ex: \"America/New_York\")" 
 164     echoerr 
"    -t, --temp-dir [path dir]" 
 165     echoerr 
"            Specify parent directory for temporary working directory." 
 166     echoerr 
"            Default: \"/dev/shm\"" 
 167     echoerr 
"    -R, --recipient-dir [path dir]" 
 168     echoerr 
"            Specify directory containing files whose first lines are" 
 169     echoerr 
"            to be interpreted as pubkey strings (see \\'-r\\' option)." 
 170     echoerr 
"    -b, --buffer-ttl [integer]" 
 171     echoerr 
"            Specify custom buffer period in seconds (default: 300 seconds)" 
 172     echoerr 
"    -B, --script-ttl [integer]" 
 173     echoerr 
"            Specify custom script time-to-live in seconds (default: \"day\")" 
 175     echoerr 
"EXAMPLE: (bash script lines)" 
 176     echoerr 
"/bin/bash bkgpslog -v -e -c \\" 
 177     echoerr 
"-z \"UTC\" -t \"/dev/shm\" \\" 
 178     echoerr 
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\" 
 179     echoerr 
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\" 
 180     echoerr 
"-o ~/Sync/Location" 
 181 } # Display information on how to use this script. 
 183     echoerr 
"$SCRIPT_VERSION" 
 184 } # Display script version. 
 186     # Usage: vbm "DEBUG:verbose message here" 
 187     # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true". 
 189     #   - OPTION_VERBOSE  variable set by processArguments function. (ex: "true", "false") 
 190     #   - "$@"            positional arguments fed to this function. 
 192     # Script function dependencies: echoerr 
 193     # External function dependencies: echo 
 194     # Last modified: 2020-04-11T23:57Z 
 195     # Last modified by: Steven Baltakatei Sandoval 
 199     if [ "$OPTION_VERBOSE" = "true" ]; then 
 200         FUNCTION_TIME
=$(date --iso-8601=ns); # Save current time in nano seconds. 
 201         echoerr 
"[$FUNCTION_TIME] ""$*"; # Display argument text. 
 205     return 0; # Function finished. 
 206 } # Verbose message display function. 
 208     while [ ! $# -eq 0 ]; do   # While number of arguments ($#) is not (!) equal to (-eq) zero (0). 
 209         #echoerr "DEBUG:Starting processArguments while loop." 
 210         #echoerr "DEBUG:Provided arguments are:""$*" 
 212             -h | --help) showUsage
; exit 1;; # Display usage. 
 213             --version) showVersion
; exit 1;; # Show version 
 214             -v | --verbose) OPTION_VERBOSE
="true"; vbm 
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. 
 215             -o | --output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm 
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory. 
 216             -e | --encrypt) OPTION_ENCRYPT
="true"; vbm 
"DEBUG:Encrypted output mode enabled.";; # Enable encryption 
 217             -r | --recipient) OPTION_RECIPIENTS
="true"; argRecPubKeys
+=("$2"); vbm 
"STATUS:pubkey added:""$2"; shift;; # Add recipients 
 218             -c | --compress) OPTION_COMPRESS
="true"; vbm 
"DEBUG:Compressed output mode enabled.";; # Enable compression 
 219             -z | --time-zone) try setTimeZoneEV 
"$2"; shift;; # Set timestamp timezone 
 220             -t | --temp-dir) OPTION_TMPDIR
="true" && argTempDirPriority
="$2"; shift;; # Set time zone 
 221             -R | --recipient-dir) OPTION_RECIPIENTS
="true"; OPTION_RECDIR
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir 
 222             -b | --buffer-ttl) OPTION_CUSTOM_BUFFERTTL
="true" && argCustomBufferTTL
="$2"; shift;; # Set custom buffer period (default: 300 seconds) 
 223             -B | --script-ttl) OPTION_CUSTOM_SCRIPTTTL_TE
="true" && argCustomScriptTTL
="$2"; shift;; # Set custom script TTL (default: "day") 
 224             *) echoerr 
"ERROR: Unrecognized argument: $1"; echoerr 
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. 
 228 } # Argument Processing 
 230     # Desc: Set time zone environment variable TZ 
 231     # Usage: setTimeZoneEV arg1 
 232     # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") 
 233     #        TZDIR env var (optional; default: "/usr/share/zoneinfo") 
 235     #         exit code 0 on success 
 236     #         exit code 1 on incorrect number of arguments 
 237     #         exit code 2 if unable to validate arg1 
 238     # Depends: yell, printenv, bash 5 
 239     # Tested on: Debian 10 
 241     local tzDir returnState
 
 242     if ! [[ $# -eq 1 ]]; then 
 243         yell 
"ERROR:Invalid argument count."; 
 247     # Read TZDIR env var if available 
 248     if printenv TZDIR 
1>/dev
/null 
2>&1; then 
 249         tzDir
="$(printenv TZDIR)"; 
 251         tzDir
="/usr/share/zoneinfo"; 
 255     if ! [[ -f "$tzDir"/"$ARG1" ]]; then 
 256         yell 
"ERROR:Invalid time zone argument."; 
 259     # Export ARG1 as TZ environment variable 
 260         TZ
="$ARG1" && export TZ 
&& returnState
="true"; 
 263     # Determine function return code 
 264     if [ "$returnState" = "true" ]; then 
 267 } # Exports TZ environment variable 
 269     # Desc: Report seconds until next day. 
 271     # Output: stdout: integer seconds until next day 
 272     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 273     # Usage: timeUntilNextDay 
 274     # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi 
 275     # Depends: date 8, echo 8, yell, try 
 277     local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
 
 279     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 280     TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. 
 281     SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second). 
 282     if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then 
 284     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then 
 285         returnState
="warning_zero"; 
 286         yell 
"WARNING:Reported time until next day exactly zero."; 
 287     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then 
 288         returnState
="warning_negative"; 
 289         yell 
"WARNING:Reported time until next day is negative."; 
 292     try 
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report 
 294     # Determine function return code 
 295     if [[ "$returnState" = "true" ]]; then 
 297     elif [[ "$returnState" = "warning_zero" ]]; then 
 299     elif [[ "$returnState" = "warning_negative" ]]; then 
 302 } # Report seconds until next day 
 304     # Desc: Report seconds until next hour 
 306     # Output: stdout: integer seconds until next hour 
 307     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 308     # Usage: timeUntilNextHour 
 309     # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi 
 311     local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
 
 312     TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 313     TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. 
 314     SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second). 
 315     if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then 
 317     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then 
 318         returnState
="warning_zero"; 
 319         yell 
"WARNING:Reported time until next hour exactly zero."; 
 320     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then 
 321         returnState
="warning_negative"; 
 322         yell 
"WARNING:Reported time until next hour is negative."; 
 325     try 
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report 
 327     # Determine function return code 
 328     if [[ "$returnState" = "true" ]]; then 
 330     elif [[ "$returnState" = "warning_zero" ]]; then 
 332     elif [[ "$returnState" = "warning_negative" ]]; then 
 335 } # Report seconds until next hour 
 337     # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) 
 338     # Usage: dateTimeShort ([str date]) 
 340     # Input: arg1: 'date'-parsable timestamp string (optional) 
 341     # Output: stdout: timestamp (ISO-8601, no separators) 
 343     local TIME_CURRENT TIME_CURRENT_SHORT argTime
 
 347     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 348     # Decide to parse current or supplied date 
 349     ## Check if time argument empty 
 350     if [[ -z "$argTime" ]]; then 
 351         ## T: Time argument empty, use current time 
 352         TIME_INPUT
="$TIME_CURRENT"; 
 354         ## F: Time argument exists, validate time 
 355         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 356             ### T: Time argument is valid; use it 
 357             TIME_INPUT
="$argTime"; 
 359             ### F: Time argument not valid; exit 
 360             yell 
"ERROR:Invalid time argument supplied: \"$argTime\""; yell 
"Exiting."; exit 1; 
 363     # Construct and deliver separator-les date string 
 364     TIME_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)"; 
 365     echo "$TIME_CURRENT_SHORT"; 
 366 } # Get YYYYmmddTHHMMSS±zzzz 
 368     # Desc: Date without separators (YYYYmmdd) 
 369     # Usage: dateShort ([str date]) 
 371     # Input: arg1: 'date'-parsable timestamp string (optional) 
 372     # Output: stdout: date (ISO-8601, no separators) 
 374     local TIME_CURRENT DATE_CURRENT_SHORT
 
 378     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 379     # Decide to parse current or supplied date 
 380     ## Check if time argument empty 
 381     if [[ -z "$argTime" ]]; then 
 382         ## T: Time argument empty, use current time 
 383         TIME_INPUT
="$TIME_CURRENT"; 
 385         ## F: Time argument exists, validate time 
 386         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 387             ### T: Time argument is valid; use it 
 388             TIME_INPUT
="$argTime"; 
 390             ### F: Time argument not valid; exit 
 391             yell 
"ERROR:Invalid time argument supplied: \"$argTime\""; yell 
"Exiting."; exit 1; 
 394     # Construct and deliver separator-les date string     
 395     DATE_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. 
 396     echo "$DATE_CURRENT_SHORT"; 
 399     # Desc: Given seconds, output ISO-8601 duration string 
 400     # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information 
 401     # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) 
 402     # Usage: timeDuration [1:seconds] ([2:precision]) 
 404     # Input: arg1: seconds as base 10 integer >= 0  (ex: 3601) 
 405     #        arg2: precision level (optional; default=2) 
 406     # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") 
 407     #         exit code 0: success 
 408     #         exit code 1: error_input 
 409     #         exit code 2: error_unknown 
 410     # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' 
 411     # Depends: date 8 (gnucoreutils), yell,  
 412     local returnState argSeconds argPrecision remainder precision witherPrecision
 
 413     local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
 
 414     local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
 
 415     local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
 
 417     argSeconds
="$1"; # read arg1 (seconds) 
 418     argPrecision
="$2"; # read arg2 (precision) 
 419     precision
=2; # set default precision 
 421     # Check that between one and two arguments is supplied 
 422     if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then 
 423         yell 
"ERROR:Invalid number of arguments:$# . Exiting."; 
 424         returnState
="error_input"; fi 
 426     # Check that argSeconds provided 
 427     if [[ $# -ge 1 ]]; then 
 428         ## Check that argSeconds is a positive integer 
 429         if [[ "$argSeconds" =~ ^
[[:digit
:]]+$ 
]]; then 
 432             yell 
"ERROR:argSeconds not a digit."; 
 433             returnState
="error_input"; 
 436         yell 
"ERROR:No argument provided. Exiting."; 
 440     # Consider whether argPrecision was provided 
 441     if  [[ $# -eq 2 ]]; then 
 442         # Check that argPrecision is a positive integer 
 443         if [[ "$argPrecision" =~ ^
[[:digit
:]]+$ 
]] && [[ "$argPrecision" -gt 0 ]]; then 
 444         precision
="$argPrecision"; 
 446             yell 
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; 
 447             returnState
="error_input"; 
 453     remainder
="$argSeconds" ; # seconds 
 454     ## Calculate full years Y, update remainder 
 455     fullYears
=$(( remainder / (365*24*60*60) )); 
 456     remainder
=$(( remainder - (fullYears*365*24*60*60) )); 
 457     ## Calculate full months M, update remainder 
 458     fullMonths
=$(( remainder / (30*24*60*60) )); 
 459     remainder
=$(( remainder - (fullMonths*30*24*60*60) )); 
 460     ## Calculate full days D, update remainder 
 461     fullDays
=$(( remainder / (24*60*60) )); 
 462     remainder
=$(( remainder - (fullDays*24*60*60) )); 
 463     ## Calculate full hours H, update remainder 
 464     fullHours
=$(( remainder / (60*60) )); 
 465     remainder
=$(( remainder - (fullHours*60*60) )); 
 466     ## Calculate full minutes M, update remainder 
 467     fullMinutes
=$(( remainder / (60) )); 
 468     remainder
=$(( remainder - (fullMinutes*60) )); 
 469     ## Calculate full seconds S, update remainder 
 470     fullSeconds
=$(( remainder / (1) )); 
 471     remainder
=$(( remainder - (remainder*1) )); 
 472     ## Check which fields filled 
 473     if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi 
 474     if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi 
 475     if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi 
 476     if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi 
 477     if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi 
 478     if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi 
 480     ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) 
 481     witherPrecision
="false" 
 484     if $hasYears && [[ $precision -gt 0 ]]; then 
 486         witherPrecision
="true"; 
 488         displayYears
="false"; 
 490     if $witherPrecision; then ((precision
--)); fi; 
 493     if $hasMonths && [[ $precision -gt 0 ]]; then 
 494         displayMonths
="true"; 
 495         witherPrecision
="true"; 
 497         displayMonths
="false"; 
 499     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 500         displayMonths
="true"; 
 502     if $witherPrecision; then ((precision
--)); fi; 
 505     if $hasDays && [[ $precision -gt 0 ]]; then 
 507         witherPrecision
="true"; 
 511     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 514     if $witherPrecision; then ((precision
--)); fi; 
 517     if $hasHours && [[ $precision -gt 0 ]]; then 
 519         witherPrecision
="true"; 
 521         displayHours
="false"; 
 523     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 526     if $witherPrecision; then ((precision
--)); fi; 
 529     if $hasMinutes && [[ $precision -gt 0 ]]; then 
 530         displayMinutes
="true"; 
 531         witherPrecision
="true"; 
 533         displayMinutes
="false"; 
 535     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 536         displayMinutes
="true"; 
 538     if $witherPrecision; then ((precision
--)); fi; 
 542     if $hasSeconds && [[ $precision -gt 0 ]]; then 
 543         displaySeconds
="true"; 
 544         witherPrecision
="true"; 
 546         displaySeconds
="false"; 
 548     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 549         displaySeconds
="true"; 
 551     if $witherPrecision; then ((precision
--)); fi; 
 553     ## Determine whether or not the "T" separator is needed to separate date and time elements 
 554     if ( $displayHours || $displayMinutes || $displaySeconds); then 
 555         displayDateTime
="true"; else displayDateTime
="false"; fi 
 557     ## Construct duration output string 
 559     if $displayYears; then 
 560         OUTPUT
=$OUTPUT$fullYears"Y"; fi 
 561     if $displayMonths; then 
 562         OUTPUT
=$OUTPUT$fullMonths"M"; fi 
 563     if $displayDays; then 
 564         OUTPUT
=$OUTPUT$fullDays"D"; fi 
 565     if $displayDateTime; then 
 566         OUTPUT
=$OUTPUT"T"; fi 
 567     if $displayHours; then 
 568         OUTPUT
=$OUTPUT$fullHours"H"; fi 
 569     if $displayMinutes; then 
 570         OUTPUT
=$OUTPUT$fullMinutes"M"; fi 
 571     if $displaySeconds; then 
 572         OUTPUT
=$OUTPUT$fullSeconds"S"; fi 
 574     ## Output duration string to stdout 
 575     echo "$OUTPUT" && returnState
="true"; 
 577     #===Determine function return code=== 
 578     if [ "$returnState" = "true" ]; then 
 580     elif [ "$returnState" = "error_input" ]; then 
 584         yell 
"ERROR:Unknown"; 
 588 } # Get duration (ex: PT10M4S ) 
 590     # Desc: Displays missing apps, files, and dirs 
 591     # Usage: displayMissing 
 592     # Input: associative arrays: appRollCall, fileRollCall, dirRollCall 
 593     # Output: stderr messages 
 594     #==BEGIN Display errors== 
 595     #===BEGIN Display Missing Apps=== 
 596     missingApps
="Missing apps  :" 
 597     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
 598     for key 
in "${!appRollCall[@]}"; do 
 599         value
="${appRollCall[$key]}" 
 600         if [ "$value" = "false" ]; then 
 601             #echo "DEBUG:Missing apps: $key => $value"; 
 602             missingApps
="$missingApps""$key " 
 606     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 607         echo "$missingApps" 1>&2; 
 609     #===END Display Missing Apps=== 
 611     #===BEGIN Display Missing Files=== 
 612     missingFiles
="Missing files:" 
 613     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done 
 614     for key 
in "${!fileRollCall[@]}"; do 
 615         value
="${fileRollCall[$key]}" 
 616         if [ "$value" = "false" ]; then 
 617             #echo "DEBUG:Missing files: $key => $value"; 
 618             missingFiles
="$missingFiles""$key " 
 622     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 623         echo "$missingFiles" 1>&2; 
 625     #===END Display Missing Files=== 
 627     #===BEGIN Display Missing Directories=== 
 628     missingDirs
="Missing dirs:" 
 629     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done 
 630     for key 
in "${!dirRollCall[@]}"; do 
 631         value
="${dirRollCall[$key]}" 
 632         if [ "$value" = "false" ]; then 
 633             #echo "DEBUG:Missing dirs: $key => $value"; 
 634             missingDirs
="$missingDirs""$key " 
 638     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 639         echo "$missingDirs" 1>&2; 
 641     #===END Display Missing Directories=== 
 643     #==END Display errors== 
 644 } # Display missing apps, files, dirs 
 645 magicSetScriptTTL
() { 
 646     #Desc: Sets script_TTL seconds from provided time_element string argument 
 647     #Usage: magicSetScriptTTL [str time_element] 
 648     #Input: arg1: string (Ex: SCRIPT_TTL_TE; "day" or "hour") 
 649     #Output: var: SCRIPT_TTL (integer seconds) 
 650     #Depends: timeUntilNextHour, timeUntilNextDay 
 653     if [[ "$argTimeElement" = "day" ]]; then 
 654             # Set script lifespan to end at start of next day 
 655         if ! SCRIPT_TTL
="$(timeUntilNextDay)"; then 
 656             if [[ "$SCRIPT_TTL" -eq 0 ]]; then 
 657             ((SCRIPT_TTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 659             yell 
"ERROR: timeUntilNextDay exit code $?"; exit 1; 
 662     elif [[ "$argTimeElement" = "hour" ]]; then 
 663         # Set script lifespan to end at start of next hour 
 664         if ! SCRIPT_TTL
="$(timeUntilNextHour)"; then 
 665             if [[ "$SCRIPT_TTL" -eq 0 ]]; then 
 666                 ((SCRIPT_TTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 668                 yell 
"ERROR: timeUntilNextHour exit code $?"; exit 1; 
 672         yell 
"ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1; 
 674 } # Seconds until next (day|hour). 
 676     # Desc: Checks that a valid tar archive exists, creates one otherwise 
 677     # Usage: checkMakeTar [ path ] 
 679     # Input: arg1: path of tar archive 
 680     # Output: exit code 0 : tar readable 
 681     #         exit code 1 : tar missing; created 
 682     #         exit code 2 : tar not readable; moved; replaced 
 683     # Depends: try, tar, date 
 684     local PATH_TAR returnFlag0 returnFlag1 returnFlag2
 
 687     # Check if file is a valid tar archive 
 688     if tar --list --file="$PATH_TAR" 1>/dev
/null 
2>&1; then 
 689         ## T1: return success 
 690         returnFlag0
="tar valid"; 
 692         ## F1: Check if file exists 
 693         if [[ -f "$PATH_TAR" ]]; then 
 695             try 
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
 
 696                 returnFlag1
="tar moved"; 
 701         ## F2: Create tar archive, return 0 
 702         try 
tar --create --file="$PATH_TAR" --files-from=/dev
/null 
&& \
 
 703             returnFlag2
="tar created"; 
 706     # Determine function return code 
 707     if [[ "$returnFlag0" = "tar valid" ]]; then 
 709     elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then 
 710         return 1; # tar missing so created 
 711     elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then 
 712         return 2; # tar not readable so moved; replaced 
 714 } # checks if arg1 is tar; creates one otherwise 
 716     # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar 
 717     # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 719     # Input: arg1: data to be written 
 720     #        arg2: file name of file to be inserted into tar 
 721     #        arg3: tar archive path (must exist first) 
 722     #        arg4: temporary working dir 
 723     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 724     # Output: file written to disk 
 725     # Example: decrypt multiple large files in parallel 
 726     #          appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 727     #          appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 728     #          appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 730     # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 
 733     local FN
="${FUNCNAME[0]}"; 
 734     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 737     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 739     # Check tar path is a file 
 740     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 743     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 745     # Set command strings 
 746     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 747     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 748     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 749     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 755     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 756     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 757     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 758     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 759     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 760     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 761     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 762     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 764     # Write to temporary working dir 
 765     eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME"; 
 768     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 769     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 770 } # Append Bash var to file appended to Tar archive 
 772     # Desc: Processes first file and then appends to tar 
 773     # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 775     # Input: arg1: path of file to be (processed and) written 
 776     #        arg2: name to use for file inserted into tar 
 777     #        arg3: tar archive path (must exist first) 
 778     #        arg4: temporary working dir 
 779     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 780     # Output: file written to disk 
 781     # Example: decrypt multiple large files in parallel 
 782     #          appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 783     #          appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 784     #          appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 788     local FN
="${FUNCNAME[0]}"; 
 789     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 792     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 793     # Check tar path is a file 
 794     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 796     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 797     # Set command strings 
 798     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 799     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 800     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 801     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 803     # Input command string 
 807     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 808     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 809     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 810     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 811     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 812     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 813     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 814     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 816     # Write to temporary working dir 
 817     eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME"; 
 820     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 821     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 822 } # Append file to Tar archive 
 824     # Desc: Checks if string is an age-compatible pubkey 
 825     # Usage: checkAgePubkey [str pubkey] 
 827     # Input: arg1: string 
 828     # Output: return code 0: string is age-compatible pubkey 
 829     #         return code 1: string is NOT an age-compatible pubkey 
 830     #         age stderr (ex: there is stderr if invalid string provided) 
 831     # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 ) 
 835     if echo "test" | age 
-a -r "$argPubkey" 1>/dev
/null
; then 
 842     # Desc: Validates Input 
 843     # Usage: validateInput [str input] [str input type] 
 845     # Input: arg1: string to validate 
 846     #        arg2: string specifying input type (ex:"ssh_pubkey") 
 847     # Output: return code 0: if input string matched specified string type 
 848     # Depends: bash 5, yell 
 851     local FN
="${FUNCNAME[0]}"; 
 856     if [[ $# -gt 2 ]]; then yell 
"ERROR:$0:$FN:Too many arguments."; exit 1; fi; 
 859     if [[ -z "$argInput" ]]; then return 1; fi 
 863     ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") 
 864     if [[ "$argType" = "ssh_pubkey" ]]; then 
 865         if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\ ]*[[:alnum
:]+/=]*$ 
]]; then 
 869     ### Check for age1[:bech32:] 
 870     if [[ "$argType" = "age_pubkey" ]]; then 
 871         if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$ 
]]; then 
 875     if [[ "$argType" = "integer" ]]; then 
 876         if [[ "$argInput" =~ ^
[[:digit
:]]*$ 
]]; then 
 879     ## time element (year, month, week, day, hour, minute, second) 
 880     if [[ "$argType" = "time_element" ]]; then 
 881         if [[ "$argInput" = "year" ]] || \
 
 882                [[ "$argInput" = "month" ]] || \
 
 883                [[ "$argInput" = "week" ]] || \
 
 884                [[ "$argInput" = "day" ]] || \
 
 885                [[ "$argInput" = "hour" ]] || \
 
 886                [[ "$argInput" = "minute" ]] || \
 
 887                [[ "$argInput" = "second" ]]; then 
 890     # Return error if no condition matched. 
 892 } # Validates strings 
 894     # Desc: Get epoch nanoseconds 
 897     # Input: arg1: 'date'-parsable timestamp string (optional) 
 898     # Output: Nanoseconds since 1970-01-01 
 899     # Depends: date 8, yell() 
 900     # Ref/Attrib: Force base 10 Bash arith with '10#'. https://stackoverflow.com/a/24777667 
 901     local TIME_CURRENT TIME_INPUT TIME_EPOCH_FLOAT TIME_EPOCH_NSFRAC
 
 907     TIME_CURRENT
="$(date --iso-8601=ns)"; # Produce `date`-parsable current timestamp with resolution of 1 nanosecond. 
 909     # Decide to parse current or supplied time 
 910     ## Check if time argument empty 
 911     if [[ -z "$argTime" ]]; then 
 912         ## T: Time argument empty, use current time 
 913         TIME_INPUT
="$TIME_CURRENT"; 
 915         ## F: Time argument exists, validate time 
 916         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 917             ### T: Time argument is valid; use it 
 918             TIME_INPUT
="$argTime"; 
 920             ### F: Time argument not valid; exit 
 921             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 924     # Construct and deliver nanoseconds since 1970-01-01     
 925     TIME_EPOCH_FLOAT
="$(date --date="$TIME_INPUT" +%s.%N)"; # Save ssss.NNNNNNNNN 
 926     TIME_EPOCH_INT
="$(echo "$TIME_EPOCH_FLOAT" | cut -d. -f1)"; # Get ssss 
 927     TIME_EPOCH_NSFRAC
="$(echo "$TIME_EPOCH_FLOAT" | cut -d. -f2)"; # Get NNNNNNNNN 
 928     TIME_EPOCH_NS
="$(( (10#"$TIME_EPOCH_INT" * 10**9) + (10#"$TIME_EPOCH_NSFRAC") ))"; 
 929     echo "$TIME_EPOCH_NS"; 
 930 } # Nanoseconds since 1970-01-01 
 931 magicBufferSleepPID
() { 
 932     # Desc: Compensates for lag so buffer rounds start every BUFFER_TTL seconds 
 933     # Input: vars: BUFFER_TTL, errResetx10e3, K_P, T_I, T_D 
 934     # # Input: array: errorHistory 
 935     # Output: vars: BUFFER_TTL_ADJ_FLOAT 
 936     # Re/Attrib: https://en.wikipedia.org/wiki/PID_controller#Standard_versus_parallel_(ideal)_form 
 938     local timeBufferStartNS timeBufferStartNSExp errNS errNSx10e3
 
 939     local errResetx10e3 errRatex10e3 ADJ BUFFER_TTL_ADJ_NS BUFFER_TTL_ADJ_INT
 
 940     local BUFFER_TTL_ADJ_FLOATFRAC
 
 941     # local errorHistorySize 
 943     # ## Define errorHistorySize 
 944     # errorHistorySize=100; 
 945     ## Define BUFFER_TTL in nanoseconds 
 946     BUFFER_TTL_NS
=$((BUFFER_TTL * 10**9)) && vbm 
"BUFFER_TTL_NS:$BUFFER_TTL_NS"; 
 948     ### PID Control factors 
 949     K_P
=1; # Gain for compensating buffer round lag 
 950     T_I
="$(((4)*BUFFER_TTL_NS/(1)))"; # Consider this number of past nanoseconds to eliminate error 
 951     T_D
="$(((1)*BUFFER_TTL_NS/(1)))"; # Predict value this number of nanoseconds into the future 
 953     # Calculate Error, errNS, in nanoseconds 
 955     timeBufferStartNS
="$(timeEpochNS)" && vbm 
"timeBufferStartNS   :$timeBufferStartNS"; 
 956     ## Calculate expected time (from start time, current buffer round number, nominal BUFFER_TTL) 
 957     timeBufferStartNSExp
="$(( (timeBufferFirstNS) + (BUFFER_TTL_NS * bufferRound) ))" && vbm 
"timeBufferStartNSExp:$timeBufferStartNSExp"; 
 958     ## Calculate error (diff between timeBufferStartNSExp and timeBufferStartNS; usually negative) 
 959     errNS
="$(( timeBufferStartNSExp - timeBufferStartNS ))" && vbm 
"errNS:$errNS"; 
 960 #    errNSx10e3="$((errNS*10**3))" && vbm "errNSx10e3:$errNSx10e3"; 
 961     # ## Append error to errorHistory 
 962     # errorHistory+=("errNS"); 
 963     # ### Trim errorHistory array if over errorHistorySize 
 964     # while [[ "${#errorHistory[@]}" -gt "errorHistorySize" ]]; then do 
 965     #   unset "errorHistory[0]"; # remove oldest entry, creating sparse array 
 966     #   errorHistory=("${errorHistory[@]}"); # reindex sparse array 
 967     #   vbm "STATUS:Trimmed errorHistory array. Entry count:${#errorHistory[@]}"; 
 970     # Calculate errReset in nanoseconds^2 
 971     ## errReset = int(errHistory(t),wrt(delta_BUFFER_TTL)) 
 972     ## Integrate errorHistory with respect to time 
 973     # for value in "${errorHistory[@]}"; do 
 974     #   errReset=$(( errReset + ( value*BUFFER_TTL_NS ) )); 
 976     vbm 
"errReset(orig):$errReset" 
 977     errReset
="$(( (errReset + (errNS*BUFFER_TTL_NS)) ))" && vbm 
"errReset(post):$errReset"; 
 978 #    errResetx10e3="$(( ( errResetx10e3 + ( errNSx10e3 * BUFFER_TTL_NS ) )*10**3 ))" && vbm "errResetx10e3:$errResetx10e3"; 
 980     # Calculate errRate in nanoseconds per nanosecond 
 981     errRate
="$(( errNS / BUFFER_TTL_NS ))" && vbm 
"errRate:$errRate"; 
 982 #    errRatex10e3="$(( ( errNSx10e3 ) / BUFFER_TTL_NS ))" && vbm "errRatex10e3:$errRatex10e3"; 
 986     vbm 
"errResetTerm:$((errReset/T_I))"; 
 987     vbm 
"errRateTerm :$((errRate*T_D))"; 
 989     # Calculate PID control signal 
 990     ## ADJ = K_P * (errNS + errReset/T_I + errRate*T_D) 
 991     ADJ
="$(( K_P*(errNS + errReset/T_I + errRate*T_D) ))" && vbm 
"ADJ:$ADJ"; 
 992 #    ADJ="$((K_P*(errNSx10e3 + (errResetx10e3/T_I) + (errRatex10e3*T_D) )/(10**3)))" && vbm "ADJ:$ADJ"; 
 994     # Calculate BUFFER_TTL_ADJ_FLOAT from ADJ (ns) 
 995     ## Calculate BUFFER_TTL_ADJ in nanoseconds (BUFFER_TTL_ADJ_NS = BUFFER_TTL_NS + ADJ) 
 996     BUFFER_TTL_ADJ_NS
="$((BUFFER_TTL_NS + ADJ))" && vbm 
"BUFFER_TTL_ADJ_NS:$BUFFER_TTL_ADJ_NS"; 
 997     ## Calculate integer seconds 
 998     BUFFER_TTL_ADJ_INT
="$((BUFFER_TTL_ADJ_NS/(10**9)))" && vbm 
"BUFFER_TTL_ADJ_INT:$BUFFER_TTL_ADJ_INT"; 
 999     ### Catch negative integer seconds, set minimum of BUFFER_TTL/10 seconds 
1000     if [[ "$BUFFER_TTL_ADJ_INT" -le "$((BUFFER_TTL/10))" ]]; then 
1001         BUFFER_TTL_ADJ_INT
="$((BUFFER_TTL/10))"; 
1002         yell 
"WARNING:Buffer lag adjustment yielded negative seconds."; 
1004     ## Calculate nanosecond remainder 
1006     BUFFER_TTL_ADJ_FLOATFRAC
="$((BUFFER_TTL_ADJ_NS - (BUFFER_TTL_ADJ_INT*(10**9)) ))" && vbm 
"BUFFER_TTL_ADJ_FLOATFRAC:$BUFFER_TTL_ADJ_FLOATFRAC"; 
1007     ### Calc absolute value of fraction (by removing '-' if present; see https://stackoverflow.com/a/47240327 
1008     BUFFER_TTL_ADJ_FLOATFRAC
="${BUFFER_TTL_ADJ_FLOATFRAC#-}" && vbm 
"BUFFER_TTL_ADJ_FLOATFRAC:$BUFFER_TTL_ADJ_FLOATFRAC"; 
1009     ## Form float BUFFER_TTL_ADJ_FLOAT 
1010     BUFFER_TTL_ADJ_FLOAT
="$BUFFER_TTL_ADJ_INT".
"$BUFFER_TTL_ADJ_FLOATFRAC" && vbm 
"BUFFER_TTL_ADJ_FLOAT:$BUFFER_TTL_ADJ_FLOAT"; 
1011     vbm 
"STATUS:Calculated adjusted BUFFER_TTL (seconds):$BUFFER_TTL_ADJ_FLOAT"; 
1012 } # Calc BUFFER_TTL_ADJ_FLOAT so buffer starts every BUFFER_TTL seconds 
1013 magicWriteVersion
() { 
1014     # Desc: Appends time-stamped VERSION to PATHOUT_TAR 
1015     # Usage: magicWriteVersion 
1017     # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP 
1018     # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME 
1019     # Output: appends tar PATHOUT_TAR 
1020     # Depends: dateTimeShort, appendArgTar 
1021     local CONTENT_VERSION pubKeyIndex
 
1023     # Set VERSION file name 
1024     FILEOUT_VERSION
="$(dateTimeShort)..VERSION"; 
1026     # Gather VERSION data in CONTENT_VERSION 
1027     CONTENT_VERSION
="SCRIPT_VERSION=$SCRIPT_VERSION"; 
1028     #CONTENT_VERSION="$CONTENT_VERSION""\\n"; 
1029     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME"; 
1030     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL"; 
1031     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION"; 
1032     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL"; 
1033     CONTENT_VERSION
="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)"; 
1034     CONTENT_VERSION
="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME"; 
1035     ## Add list of recipient pubkeys 
1036     for pubkey 
in "${recPubKeysValid[@]}"; do 
1038         CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey"; 
1040     ## Process newline escapes 
1041     CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")" 
1043     # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR 
1044     appendArgTar 
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP"; 
1046 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar() 
1047 magicGatherWriteBuffer
() { 
1048     # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR 
1049     # Inputs: vars: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP, 
1050     # Inputs: vars: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX 
1051     # Output: file: (PATHOUT_TAR) 
1052     # Depends: yell(), try(), vbm(), appendArgTar(), tar 1, sleep 8, checkMakeTar() 
1053     # Depends: magicWriteVersion(), appendFileTar() 
1056     vbm 
"DEBUG:STATUS:$FN:Started magicGatherWriteBuffer()."; 
1057     # Debug:Get function name 
1058     FN
="${FUNCNAME[0]}"; 
1060     # Create buffer file with unique name 
1061     PATHOUT_BUFFER
="$DIR_TMP/buffer$SECONDS" && vbm 
"PATHOUT_BUFFER:$PATHOUT_BUFFER"; 
1063     timeout 
"$BUFFER_TTL"s gpspipe 
-r -o "$PATHOUT_BUFFER" ; 
1064     timeBufferStartLong
="$(date --date="$BUFFER_TTL seconds ago" --iso-8601=seconds)" && vbm 
"timeBufferStartLong:$timeBufferStartLong" || yell 
"ERROR:timeBufferStartLong fail"; 
1065     timeBufferStart
="$(dateTimeShort "$timeBufferStartLong" )" && vbm 
"timeBufferStart:$timeBufferStart" || yell 
"ERROR:timeBufferStart fail"; # Note start time 
1066     # Determine file paths (time is start of buffer period) 
1067     FILEOUT_BASENAME
="$timeBufferStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" && vbm 
"STATUS:Set FILEOUT_BASENAME to:$FILEOUT_BASENAME"; 
1068     ## Files saved to DIR_TMP 
1069     FILEOUT_NMEA
="$FILEOUT_BASENAME".nmea
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm 
"STATUS:Set FILEOUT_NMEA to:$FILEOUT_NMEA"; 
1070     FILEOUT_GPX
="$FILEOUT_BASENAME".gpx
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm 
"STATUS:Set FILEOUT_GPX to:$FILEOUT_GPX"; 
1071     FILEOUT_KML
="$FILEOUT_BASENAME".kml
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm 
"STATUS:Set FILEOUT_KML to:$FILEOUT_KML"; 
1072     PATHOUT_NMEA
="$DIR_TMP"/"$FILEOUT_NMEA" && vbm 
"STATUS:Set PATHOUT_NMEA to:$PATHOUT_NMEA"; 
1073     PATHOUT_GPX
="$DIR_TMP"/"$FILEOUT_GPX" && vbm 
"STATUS:Set PATHOUT_GPX to:$PATHOUT_GPX"; 
1074     PATHOUT_KML
="$DIR_TMP"/"$FILEOUT_KML" && vbm 
"STATUS:Set PATHOUT_KML to:$PATHOUT_KML"; 
1075     ## Files saved to disk (DIR_OUT) 
1076     ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar") 
1077     PATHOUT_TAR
="$DIR_OUT"/"$(dateShort "$(date --date="$BUFFER_TTL seconds ago" --iso-8601=seconds)")".."$SCRIPT_HOSTNAME""_location
""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar && \ 
1078         vbm "STATUS
:Set PATHOUT_TAR to
:$PATHOUT_TAR"; 
1080     vbm "STATUS
:FN             
:$FN"; 
1081     vbm "STATUS
:DIR_TMP        
:$DIR_TMP"; 
1082     vbm "STATUS
:PATHOUT_TAR    
:$PATHOUT_TAR"; 
1083     vbm "STATUS
:PATHOUT_NMEA   
:$PATHOUT_NMEA"; 
1084     vbm "STATUS
:PATHOUT_GPX    
:$PATHOUT_GPX"; 
1085     vbm "STATUS
:PATHOUT_KML    
:$PATHOUT_KML"; 
1086     vbm "STATUS
:BUFFER_TTL     
:$BUFFER_TTL"; 
1087     vbm "STATUS
:PATHOUT_BUFFER 
:$PATHOUT_BUFFER"; 
1088     vbm "STATUS
:timeBufferStart
:$timeBufferStart"; 
1089     vbm "FILEOUT_BASENAME      
:$FILEOUT_BASENAME"; 
1092     # Validate PATHOUT_TAR as tar. 
1093     checkMakeTar "$PATHOUT_TAR"; 
1094     ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) 
1095     vbm "exit status before magicWriteVersion
:$?
" 
1096     if [[ $? -eq 1 ]] || [[ $? -eq 2 ]]; then magicWriteVersion; fi 
1098     # Write bufferBash to PATHOUT_TAR 
1099     wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) 
1100     appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data 
1101     appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file 
1102     appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file 
1104     # Remove secured chunks from DIR_TMP 
1105     rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML"; 
1106     vbm "DEBUG
:STATUS
:$FN:Finished magicGatherWriteBuffer
().
"; 
1107 } # write buffer to disk 
1108 magicParseRecipientDir() { 
1109     # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory
") 
1110     # Inputs:  vars: OPTION_RECDIR, argRecDir, OPTION_ENCRYPT 
1111     #          arry: recPubKeysValid 
1112     # Outputs: arry: recPubKeysValid 
1113     # Depends: processArguments, 
1114     local recFileLine updateRecipients recipientDir 
1115     declare -a candRecPubKeysValid 
1117     # Check that '-e' and '-R' set 
1118     if [[ "$OPTION_ENCRYPT" = "true
" ]] && [[ "$OPTION_RECDIR" = "true
" ]]; then 
1119         ### Check that argRecDir is a directory. 
1120         if [[ -d "$argRecDir" ]]; then 
1121             recipientDir="$argRecDir" && vbm "STATUS
:Recipient watch directory detected
:\"$recipientDir\""; 
1122             #### Initialize variable indicating outcome of pubkey review 
1123             unset updateRecipients 
1124             #### Add existing recipients 
1125             candRecPubKeysValid=("${recPubKeysValidStatic[@]}"); 
1126             #### Parse files in recipientDir 
1127             for file in "$recipientDir"/*; do 
1128                 ##### Read first line of each file 
1129                 recFileLine="$(head -n1 "$file")" && vbm "STATUS
:Checking 
if pubkey
:\"$recFileLine\""; 
1130                 ##### check if first line is a valid pubkey 
1131                 if checkAgePubkey "$recFileLine" && \ 
1132                         ( validateInput "$recFileLine" "ssh_pubkey
" || validateInput "$recFileLine" "age_pubkey
"); then 
1133                     ###### T: add candidate pubkey to candRecPubKeysValid 
1134                     candRecPubKeysValid+=("$recFileLine") && vbm "STATUS
:RecDir pubkey is valid pubkey
:\"$recFileLine\""; 
1136                     ###### F: throw warning; 
1137                     yell "ERROR
:Invalid recipient 
file detected. Not modifying recipient list.
" 
1138                     updateRecipients="false
"; 
1141             #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected 
1142             if ! [[ "$updateRecipients" = "false
" ]]; then 
1143                 recPubKeysValid=("${candRecPubKeysValid[@]}") && vbm "STATUS
:Wrote candRecPubkeysValid to recPubKeysValid
:\"${recPubKeysValid[*]}\""; 
1146             yell "ERROR
:$0:Recipient directory 
$argRecDir does not exist. Exiting.
"; exit 1; 
1149     # Handle case if '-R' set but '-e' not set 
1150     if [[ ! "$OPTION_ENCRYPT" = "true
" ]] && [[ "$OPTION_RECDIR" = "true
" ]]; then 
1151         yell "ERROR
: \\'-R\\' is 
set but 
\\'-e\\' is not 
set.
"; fi; 
1152 } # Update recPubKeysValid with argRecDir 
1153 magicParseRecipientArgs() { 
1154     # Desc: Parses recipient arguments specified by '-r' option 
1155     # Input:  vars: OPTION_ENCRYPT from processArguments() 
1156     #         arry: argRecPubKeys from processArguments() 
1157     # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX 
1158     #         arry: recPubKeysValid, recPubKeysValidStatic 
1159     # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments() 
1162     # Check if encryption option active. 
1163     if [[ "$OPTION_ENCRYPT" = "true
" ]] && [[ "$OPTION_RECIPIENTS" = "true
" ]]; then  
1164         if checkapp age; then # Check that age is available. 
1165             for pubkey in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message 
1166                 vbm "DEBUG
:Testing pubkey string
:$pubkey"; 
1167                 if checkAgePubkey "$pubkey" && \ 
1168                         ( validateInput "$pubkey" "ssh_pubkey
" || validateInput "$pubkey" "age_pubkey
"); then 
1169                     #### Form age recipient string 
1170                     recipients="$recipients""-r '$pubkey' "; 
1171                     vbm "STATUS
:Added pubkey 
for forming age recipient string
:""$pubkey"; 
1172                     vbm "DEBUG
:recipients
:""$recipients"; 
1173                     #### Add validated pubkey to recPubKeysValid array 
1174                     recPubKeysValid+=("$pubkey") && vbm "DEBUG
:recPubkeysValid
:pubkey added
:$pubkey"; 
1176                     yell "ERROR
:Exit code 
""$?
"". Invalid recipient pubkey string. Exiting.
"; exit 1; 
1179             vbm "DEBUG
:Finished processing argRecPubKeys array
"; 
1180             vbm "STATUS
:Array of validated pubkeys
:${recPubKeysValid[*]}"; 
1181             recPubKeysValidStatic=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function 
1183             ##  Form age command string 
1184             CMD_ENCRYPT="age 
""$recipients " && vbm "CMD_ENCRYPT
:$CMD_ENCRYPT"; 
1185             CMD_ENCRYPT_SUFFIX=".age
" && vbm "CMD_ENCRYPT_SUFFIX
:$CMD_ENCRYPT_SUFFIX"; 
1187             yell "ERROR
:Encryption enabled but 
\"age
\" not found. Exiting.
"; exit 1; 
1190         CMD_ENCRYPT="tee /dev
/null 
" && vbm "CMD_ENCRYPT
:$CMD_ENCRYPT"; 
1191         CMD_ENCRYPT_SUFFIX="" && vbm "CMD_ENCRYPT_SUFFIX
:$CMD_ENCRYPT_SUFFIX"; 
1192         vbm "DEBUG
:Encryption not enabled.
" 
1194     # Catch case if '-e' is set but '-r' or '-R' is not 
1195     if [[ "$OPTION_ENCRYPT" = "true
" ]] && [[ ! "$OPTION_RECIPIENTS" = "true
" ]]; then 
1196         yell "ERROR
:\\'-e\\' set but no 
\\'-r\\' or 
\\'-R\\' set.
"; exit 1; fi; 
1197     # Catch case if '-r' or '-R' set but '-e' is not 
1198     if [[ ! "$OPTION_ENCRYPT" = "true
" ]] && [[ "$OPTION_RECIPIENTS" = "true
" ]]; then 
1199         yell "ERROR
:\\'-r\\' or 
\\'-R\\' set but 
\\'-e\\' is not 
set.
"; exit 1; fi;  
1200 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix 
1201 magicParseCompressionArg() { 
1202     # Desc: Parses compression arguments specified by '-c' option 
1203     # Input:  vars: OPTION_COMPRESS 
1204     # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX 
1205     # Depends: checkapp(), vbm(), gzip,  
1206     if [[ "$OPTION_COMPRESS" = "true
" ]]; then # Check if compression option active 
1207         if checkapp gzip; then # Check if gzip available 
1208             CMD_COMPRESS="gzip " && vbm "CMD_COMPRESS
:$CMD_COMPRESS"; 
1209             CMD_COMPRESS_SUFFIX=".gz
" && vbm "CMD_COMPRESS_SUFFIX
:$CMD_COMPRESS_SUFFIX"; 
1211             yell "ERROR
:Compression enabled but 
\"gzip\" not found. Exiting.
"; exit 1; 
1214         CMD_COMPRESS="tee /dev
/null 
" && vbm "CMD_COMPRESS
:$CMD_COMPRESS"; 
1215         CMD_COMPRESS_SUFFIX="" && vbm "CMD_COMPRESS_SUFFIX
:$CMD_COMPRESS_SUFFIX"; 
1216         vbm "DEBUG
:Compression not enabled.
"; 
1218 } # Form compression cmd string and filename suffix 
1219 magicInitWorkingDir() { 
1220     # Desc: Determine temporary working directory from defaults or user input 
1221     # Usage: magicInitWorkignDir 
1222     # Input:  vars: OPTION_TEMPDIR, argTempDirPriority, DIR_TMP_DEFAULT 
1223     # Input:  vars: SCRIPT_TIME_START 
1224     # Output: vars: DIR_TMP 
1225     # Depends: processArguments(), vbm(), yell() 
1226     # Parse '-t' option (user-specified temporary working dir) 
1227     ## Set DIR_TMP_PARENT to user-specified value if specified 
1228     local DIR_TMP_PARENT 
1230     if [[ "$OPTION_TMPDIR" = "true
" ]]; then 
1231         if [[ -d "$argTempDirPriority" ]]; then 
1232             DIR_TMP_PARENT="$argTempDirPriority";  
1234             yell "WARNING
:Specified temporary working directory not valid
:$argTempDirPriority"; 
1235             exit 1; # Exit since user requires a specific temp dir and it is not available. 
1238     ## Set DIR_TMP_PARENT to default or fallback otherwise 
1239         if [[ -d "$DIR_TMP_DEFAULT" ]]; then 
1240             DIR_TMP_PARENT="$DIR_TMP_DEFAULT"; 
1241         elif [[ -d /tmp ]]; then 
1242             yell "WARNING
:$DIR_TMP_DEFAULT not available. Falling back to 
/tmp .
"; 
1243             DIR_TMP_PARENT="/tmp
"; 
1245             yell "ERROR
:No valid working directory available. Exiting.
"; 
1249     ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START) 
1250     DIR_TMP="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog
" && vbm "DEBUG
:Set DIR_TMP to
:$DIR_TMP"; # Note: removed at end of main().     
1251 } # Sets working dir 
1252 magicParseCustomTTL() { 
1253     # Desc: Set user-specified TTLs for buffer and script 
1254     # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string) 
1255     # Input: vars: OPTION_CUSTOM_BUFFERTTL, OPTION_CUSTOM_SCRIPTTTL 
1256     # Input: vars: BUFFER_TTL (integer), SCRIPT_TTL_TE (string) 
1257     # Output: BUFFER_TTL (integer), SCRIPT_TTL_TE (string) 
1258     # Depends validateInput(), showUsage(), yell 
1260     # React to '-b, --buffer-ttl' option 
1261     if [[ "$OPTION_CUSTOM_BUFFERTTL" = "true
" ]]; then 
1262         ## T: Check if argCustomBufferTTL is an integer 
1263         if validateInput "$argCustomBufferTTL" "integer
"; then 
1264             ### T: argCustomBufferTTL is an integer 
1265             BUFFER_TTL="$argCustomBufferTTL" && vbm "Custom BUFFER_TTL from 
-b:$BUFFER_TTL"; 
1267             ### F: argcustomBufferTTL is not an integer 
1268             yell "ERROR
:Invalid integer argument 
for custom buffer 
time-to-live.
"; showUsage; exit 1; 
1270         ## F: do not change BUFFER_TTL 
1273     # React to '-B, --script-ttl' option 
1274     if [[ "$OPTION_CUSTOM_SCRIPTTTL_TE" = "true
" ]]; then 
1275         ## T: Check if argCustomScriptTTL is a time element (ex: "day
", "hour
") 
1276         if validateInput "$argCustomScriptTTL" "time_element
"; then 
1277             ### T: argCustomScriptTTL is a time element 
1278             SCRIPT_TTL_TE="$argCustomScriptTTL" && vbm "Custom SCRIPT_TTL_TE from 
-B:$SCRIPT_TTL_TE"; 
1280             ### F: argcustomScriptTTL is not a time element 
1281             yell "ERROR
:Invalid 
time element argument 
for custom 
script time-to-live.
"; showUsage; exit 1; 
1283         ## F: do not change SCRIPT_TTL_TE 
1285 } # Sets custom script or buffer TTL if specified 
1289     # DEBUG: Print environment variables 
1290     vbm "echo $(printenv)"; 
1292     processArguments "$@
"; 
1293     ## Act upon arguments 
1294     ### Determine working directory 
1295     magicInitWorkingDir; # Sets DIR_TMP from argTempDirPriority 
1296     ### Set output encryption and compression option strings 
1297     #### React to "-r" ("encryption recipients
") option 
1298     magicParseRecipientArgs; # Updates recPubKeysValid, CMD_ENCRYPT[_SUFFIX] from argRecPubKeys 
1299     #### React to "-c" ("compression
") option 
1300     magicParseCompressionArg; # Updates CMD_COMPRESS[_SUFFIX] 
1301     #### React to "-R" ("recipient directory
") option 
1302     magicParseRecipientDir; # Updates recPubKeysValid 
1303     #### React to custom buffer and script TTL options ("-b", "-B") 
1304     magicParseCustomTTL; # Sets custom SCRIPT_TTL_TE and/or BUFFER_TTL if specified 
1306     # Check that critical apps and dirs are available, display missing ones. 
1307     if ! checkapp gpspipe tar && ! checkdir "$DIR_OUT" "DIR_TMP
"; then 
1308         yell "ERROR
:Critical components missing.
"; 
1309         displayMissing; yell "Exiting.
"; exit 1; fi 
1311     # Set script lifespan (SCRIPT_TTL from SCRIPT_TTL_TE) 
1312     magicSetScriptTTL "$SCRIPT_TTL_TE"; 
1313     ## Note: SCRIPT_TTL_TE is time element string (ex: "day
") while SCRIPT_TTL is integer seconds 
1315     # File name substring (ISO-8601 duration from BUFFER_TTL) 
1316     bufferTTL_STR="$(timeDuration "$BUFFER_TTL")"; 
1318     # Init temp working dir 
1319     try mkdir "$DIR_TMP" && vbm "DEBUG
:Working dir creatd 
at:$DIR_TMP"; 
1321     # Initialize 'tar' archive 
1322     ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.
[.gpx.gz
].
tar")) 
1323     PATHOUT_TAR="$DIR_OUT"/"$(dateShort)".."$SCRIPT_HOSTNAME""_location
""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar && \ 
1324         vbm "STATUS
:Set PATHOUT_TAR to
:$PATHOUT_TAR"; 
1325     ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise. 
1326     checkMakeTar "$PATHOUT_TAR" && vbm "DEBUG
:Confirmed or Created to be a 
tar:$PATHOUT_TAR"; 
1327     ## Append VERSION file to PATHOUT_TAR 
1330     # Define GPS conversion commands 
1331     CMD_CONV_NMEA="tee /dev
/null 
" && vbm "STATUS
:Set CMD_CONV_NMEA to
:$CMD_CONV_NMEA"; # tee as passthrough 
1332     CMD_CONV_GPX="gpsbabel 
-i nmea 
-f - -o gpx 
-F - " && vbm "STATUS
:Set CMD_CONV_GPX to
:$CMD_CONV_GPX"; # convert NMEA to GPX 
1333     CMD_CONV_KML="gpsbabel 
-i nmea 
-f - -o kml 
-F - " && vbm "STATUS
:Set CMD_CONV_KML to
:$CMD_CONV_KML"; # convert NMEA to KML 
1335     # MAIN LOOP:Record gps data until script lifespan ends 
1336     timeBufferFirstNS="$(timeEpochNS)"; bufferRound=0; BUFFER_TTL_ADJ_FLOAT="$BUFFER_TTL"; 
1337     while [[ "$SECONDS" -lt "$SCRIPT_TTL" ]]; do 
1338         magicParseRecipientDir; 
1339         magicGatherWriteBuffer & 
1340         sleep "$BUFFER_TTL_ADJ_FLOAT"; # adjusted by magicBufferSleepPID 
1342         magicBufferSleepPID; # Calculates BUFFER_TTL_ADJ_FLOAT from BUFFER_TTL given buffer expected start time vs. actual 
1347     try rm -r "$DIR_TMP"; 
1349     vbm "STATUS
:Main 
function finished.
"; 
1351 #===END Declare local script functions=== 
1352 #==END Define script parameters== 
1355 #==BEGIN Perform work and exit== 
1356 main "$@
" # Run main function. 
1358 #==END Perform work and exit== 
1360 # Author: Steven Baltakatei Sandoval;