3 # Desc: Records gps data until midnight 
   4 # Author: Steven Baltakatei Sandoval; License: GPLv3+ 
   5 # Usage: bkgpslog -o [output dir] 
   7 #==BEGIN Define script parameters== 
   8 ## Logging Behavior parameters 
   9 BUFFER_TTL
="300"; # time between file writes 
  10 SCRIPT_TTL
="day"; # (day|hour) 
  11 #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option 
  12 DIR_TMP_DEFAULT
="/dev/shm"; # Default parent of working directory 
  14 SCRIPT_TIME_START
=$(date +%Y%m%dT%H%M%S.%N); 
  15 PATH
="$HOME/.local/bin:$PATH";   # Add "$(systemd-path user-binaries)" path in case apps saved there 
  16 SCRIPT_HOSTNAME
=$(hostname);     # Save hostname of system running this script. 
  17 SCRIPT_VERSION
="0.3.9";          # Define version of script. 
  18 SCRIPT_NAME
="bkgpslog";          # Define basename of script file. 
  19 SCRIPT_URL
="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script. 
  20 AGE_VERSION
="1.0.0-beta2";       # Define version of age (encryption program) 
  21 AGE_URL
="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age. 
  23 declare -Ag appRollCall 
# Associative array for storing app status 
  24 declare -Ag fileRollCall 
# Associative array for storing file status 
  25 declare -Ag dirRollCall 
# Associative array for storing dir status 
  26 declare -a recPubKeys 
# for processArguments function 
  27 declare recipients 
# for main function 
  29 ## Initialize variables 
  30 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
=""; 
  32 #===BEGIN Declare local script functions=== 
  34     # Desc: If arg is a command, save result in assoc array 'appRollCall' 
  35     # Usage: checkapp arg1 arg2 arg3 ... 
  36     # Input: global assoc. array 'appRollCall' 
  37     # Output: adds/updates key(value) to global assoc array 'appRollCall' 
  39     #echo "DEBUG:$(date +%S.%N)..Starting checkapp function." 
  40     #echo "DEBUG:args: $@" 
  41     #echo "DEBUG:returnState:$returnState" 
  45         #echo "DEBUG:processing arg:$arg" 
  46         if command -v "$arg" 1>/dev
/null 
2>&1; then # Check if arg is a valid command 
  47             appRollCall
[$arg]="true"; 
  48             #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]} 
  49             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  51             appRollCall
[$arg]="false"; returnState
="false"; 
  55     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
  56     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
  58     #===Determine function return code=== 
  59     if [ "$returnState" = "true" ]; then 
  60         #echo "DEBUG:checkapp returns true for $arg"; 
  63         #echo "DEBUG:checkapp returns false for $arg"; 
  66 } # Check that app exists 
  68     # Desc: If arg is a file path, save result in assoc array 'fileRollCall' 
  69     # Usage: checkfile arg1 arg2 arg3 ... 
  70     # Input: global assoc. array 'fileRollCall' 
  71     # Output: adds/updates key(value) to global assoc array 'fileRollCall'; 
  72     # Output: returns 0 if app found, 1 otherwise 
  77         #echo "DEBUG:processing arg:$arg" 
  78         if [ -f "$arg" ]; then 
  79             fileRollCall
["$arg"]="true"; 
  80             #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]} 
  81             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  83             fileRollCall
["$arg"]="false"; returnState
="false"; 
  87     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done 
  88     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
  90     #===Determine function return code=== 
  91     if [ "$returnState" = "true" ]; then 
  92         #echo "DEBUG:checkapp returns true for $arg"; 
  95         #echo "DEBUG:checkapp returns false for $arg"; 
  98 } # Check that file exists 
 100     # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' 
 101     # Usage: checkdir arg1 arg2 arg3 ... 
 102     # Input: global assoc. array 'dirRollCall' 
 103     # Output: adds/updates key(value) to global assoc array 'dirRollCall'; 
 104     # Output: returns 0 if app found, 1 otherwise 
 109         #echo "DEBUG:processing arg:$arg" 
 110         if [ -d "$arg" ]; then 
 111             dirRollCall
["$arg"]="true"; 
 112             #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]} 
 113             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
 114         elif [ "$arg" = "" ]; then 
 115             dirRollCall
["$arg"]="false"; returnState
="false"; 
 121     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done 
 122     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
 124     #===Determine function return code=== 
 125     if [ "$returnState" = "true" ]; then 
 126         #echo "DEBUG:checkapp returns true for $arg"; 
 129         #echo "DEBUG:checkapp returns false for $arg"; 
 132 } # Check that dir exists 
 134 # Yell, Die, Try Three-Fingered Claw technique 
 135 # Ref/Attrib: https://stackoverflow.com/a/25515370 
 136 yell
() { echo "$0: $*" >&2; } 
 137 die
() { yell 
"$*"; exit 111; } 
 138 try
() { "$@" || die 
"cannot $*"; } 
 141     echo "$@" 1>&2; # Define stderr echo function. 
 142 } # Define stderr message function. 
 145     echoerr 
"    bkgpslog [ options ]" 
 148     echoerr 
"    -h, --help" 
 149     echoerr 
"            Display help information." 
 152     echoerr 
"            Display script version." 
 154     echoerr 
"    -v, --verbose" 
 155     echoerr 
"            Display debugging info." 
 157     echoerr 
"    -e, --encrypt" 
 158     echoerr 
"            Encrypt output." 
 160     echoerr 
"    -r, --recipient [ pubkey string ]" 
 161     echoerr 
"            Specify recipient. May be age or ssh pubkey." 
 162     echoerr 
"            See https://github.com/FiloSottile/age" 
 164     echoerr 
"    -o, --output [ directory ]" 
 165     echoerr 
"            Specify output directory to save logs." 
 167     echoerr 
"    -c, --compress" 
 168     echoerr 
"            Compress output with gzip (before encryption if enabled)." 
 170     echoerr 
"    -z, --time-zone" 
 171     echoerr 
"            Specify time zone. (ex: \"America/New_York\")" 
 173     echoerr 
"    -t, --temp-dir" 
 174     echoerr 
"            Specify parent directory for temporary working directory." 
 175     echoerr 
"            Default: \"/dev/shm\"" 
 177     echoerr 
"EXAMPLE: (bash script lines)" 
 178     echoerr 
"/bin/bash bkgpslog -e -c \\" 
 179     echoerr 
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\" 
 180     echoerr 
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\" 
 181     echoerr 
"-o ~/Sync/Location" 
 182 } # Display information on how to use this script. 
 184     echoerr 
"$SCRIPT_VERSION" 
 185 } # Display script version. 
 187     # Usage: vbm "DEBUG:verbose message here" 
 188     # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true". 
 190     #   - OPTION_VERBOSE  variable set by processArguments function. (ex: "true", "false") 
 191     #   - "$@"            positional arguments fed to this function. 
 193     # Script function dependencies: echoerr 
 194     # External function dependencies: echo 
 195     # Last modified: 2020-04-11T23:57Z 
 196     # Last modified by: Steven Baltakatei Sandoval 
 200     if [ "$OPTION_VERBOSE" = "true" ]; then 
 201         FUNCTION_TIME
=$(date --iso-8601=ns); # Save current time in nano seconds. 
 202         echoerr 
"[$FUNCTION_TIME] ""$*"; # Display argument text. 
 206     return 0; # Function finished. 
 207 } # Verbose message display function. 
 209     while [ ! $# -eq 0 ]; do   # While number of arguments ($#) is not (!) equal to (-eq) zero (0). 
 210         #echoerr "DEBUG:Starting processArguments while loop." 
 211         #echoerr "DEBUG:Provided arguments are:""$*" 
 213             -h | --help) showUsage
; exit 1;; # Display usage. 
 214             --version) showVersion
; exit 1;; # Show version 
 215             -v | --verbose) OPTION_VERBOSE
="true"; vbm 
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. 
 216             -o | --output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm 
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory. 
 217             -e | --encrypt) OPTION_ENCRYPT
="true"; vbm 
"DEBUG:Encrypted output mode enabled.";; 
 218             -r | --recipient) # Add 'age' recipient via public key string 
 219                 recPubKeys
+=("$2"); vbm 
"STATUS:pubkey added:""$2"; shift;; 
 220             -c | --compress) OPTION_COMPRESS
="true"; vbm 
"DEBUG:Compressed output mode enabled.";; 
 221             -z | --time-zone) try setTimeZoneEV 
"$2"; shift;; 
 222             -t | --temp-dir) OPTION_TMPDIR
="true" && TMP_DIR_PRIORITY
="$2"; shift;; 
 223             *) echoerr 
"ERROR: Unrecognized argument: $1"; echoerr 
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. 
 227 } # Argument Processing 
 229     # Desc: Set time zone environment variable TZ 
 230     # Usage: setTimeZoneEV arg1 
 231     # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") 
 232     #        TZDIR env var (optional; default: "/usr/share/zoneinfo") 
 234     #         exit code 0 on success 
 235     #         exit code 1 on incorrect number of arguments 
 236     #         exit code 2 if unable to validate arg1 
 237     # Depends: yell, printenv, bash 5 
 238     # Tested on: Debian 10 
 240     local tzDir returnState
 
 241     if ! [[ $# -eq 1 ]]; then 
 242         yell 
"ERROR:Invalid argument count."; 
 246     # Read TZDIR env var if available 
 247     if printenv TZDIR 
1>/dev
/null 
2>&1; then 
 248         tzDir
="$(printenv TZDIR)"; 
 250         tzDir
="/usr/share/zoneinfo"; 
 254     if ! [[ -f "$tzDir"/"$ARG1" ]]; then 
 255         yell 
"ERROR:Invalid time zone argument."; 
 258     # Export ARG1 as TZ environment variable 
 259         TZ
="$ARG1" && export TZ 
&& returnState
="true"; 
 262     # Determine function return code 
 263     if [ "$returnState" = "true" ]; then 
 266 } # Exports TZ environment variable 
 268     # Desc: Report seconds until next day. 
 270     # Output: stdout: integer seconds until next day 
 271     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 272     # Usage: timeUntilNextDay 
 273     # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi 
 274     # Depends: date 8, echo 8, yell, try 
 276     local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
 
 278     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 279     TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. 
 280     SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second). 
 281     if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then 
 283     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then 
 284         returnState
="warning_zero"; 
 285         yell 
"WARNING:Reported time until next day exactly zero."; 
 286     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then 
 287         returnState
="warning_negative"; 
 288         yell 
"WARNING:Reported time until next day is negative."; 
 291     try 
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report 
 293     # Determine function return code 
 294     if [[ "$returnState" = "true" ]]; then 
 296     elif [[ "$returnState" = "warning_zero" ]]; then 
 298     elif [[ "$returnState" = "warning_negative" ]]; then 
 301 } # Report seconds until next day 
 303     # Desc: Report seconds until next hour 
 305     # Output: stdout: integer seconds until next hour 
 306     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 307     # Usage: timeUntilNextHour 
 308     # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi 
 310     local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
 
 311     TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 312     TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. 
 313     SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second). 
 314     if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then 
 316     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then 
 317         returnState
="warning_zero"; 
 318         yell 
"WARNING:Reported time until next hour exactly zero."; 
 319     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then 
 320         returnState
="warning_negative"; 
 321         yell 
"WARNING:Reported time until next hour is negative."; 
 324     try 
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report 
 326     # Determine function return code 
 327     if [[ "$returnState" = "true" ]]; then 
 329     elif [[ "$returnState" = "warning_zero" ]]; then 
 331     elif [[ "$returnState" = "warning_negative" ]]; then 
 334 } # Report seconds until next hour 
 336     # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) 
 337     # Usage: dateTimeShort ([str date]) 
 339     # Input: arg1: 'date'-parsable timestamp string (optional) 
 340     # Output: stdout: timestamp (ISO-8601, no separators) 
 342     local TIME_CURRENT TIME_CURRENT_SHORT
 
 346     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 347     # Decide to parse current or supplied date 
 348     ## Check if time argument empty 
 349     if [[ -z "$argTime" ]]; then 
 350         ## T: Time argument empty, use current time 
 351         TIME_INPUT
="$TIME_CURRENT"; 
 353         ## F: Time argument exists, validate time 
 354         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 355             ### T: Time argument is valid; use it 
 356             TIME_INPUT
="$argTime"; 
 358             ### F: Time argument not valid; exit 
 359             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 362     # Construct and deliver separator-les date string 
 363     TIME_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)"; 
 364     echo "$TIME_CURRENT_SHORT"; 
 365 } # Get YYYYmmddTHHMMSS±zzzz 
 367     # Desc: Date without separators (YYYYmmdd) 
 368     # Usage: dateShort ([str date]) 
 370     # Input: arg1: 'date'-parsable timestamp string (optional) 
 371     # Output: stdout: date (ISO-8601, no separators) 
 373     local TIME_CURRENT DATE_CURRENT_SHORT
 
 377     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 378     # Decide to parse current or supplied date 
 379     ## Check if time argument empty 
 380     if [[ -z "$argTime" ]]; then 
 381         ## T: Time argument empty, use current time 
 382         TIME_INPUT
="$TIME_CURRENT"; 
 384         ## F: Time argument exists, validate time 
 385         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 386             ### T: Time argument is valid; use it 
 387             TIME_INPUT
="$argTime"; 
 389             ### F: Time argument not valid; exit 
 390             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 393     # Construct and deliver separator-les date string     
 394     DATE_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. 
 395     echo "$DATE_CURRENT_SHORT"; 
 398     # Desc: Given seconds, output ISO-8601 duration string 
 399     # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information 
 400     # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) 
 401     # Usage: timeDuration [1:seconds] ([2:precision]) 
 403     # Input: arg1: seconds as base 10 integer >= 0  (ex: 3601) 
 404     #        arg2: precision level (optional; default=2) 
 405     # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") 
 406     #         exit code 0: success 
 407     #         exit code 1: error_input 
 408     #         exit code 2: error_unknown 
 409     # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' 
 410     # Depends: date 8 (gnucoreutils), yell,  
 411     local returnState argSeconds argPrecision remainder precision witherPrecision
 
 412     local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
 
 413     local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
 
 414     local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
 
 416     argSeconds
="$1"; # read arg1 (seconds) 
 417     argPrecision
="$2"; # read arg2 (precision) 
 418     precision
=2; # set default precision 
 420     # Check that between one and two arguments is supplied 
 421     if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then 
 422         yell 
"ERROR:Invalid number of arguments:$# . Exiting."; 
 423         returnState
="error_input"; fi 
 425     # Check that argSeconds provided 
 426     if [[ $# -ge 1 ]]; then 
 427         ## Check that argSeconds is a positive integer 
 428         if [[ "$argSeconds" =~ ^
[[:digit
:]]+$ 
]]; then 
 431             yell 
"ERROR:argSeconds not a digit."; 
 432             returnState
="error_input"; 
 435         yell 
"ERROR:No argument provided. Exiting."; 
 439     # Consider whether argPrecision was provided 
 440     if  [[ $# -eq 2 ]]; then 
 441         # Check that argPrecision is a positive integer 
 442         if [[ "$argPrecision" =~ ^
[[:digit
:]]+$ 
]] && [[ "$argPrecision" -gt 0 ]]; then 
 443         precision
="$argPrecision"; 
 445             yell 
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; 
 446             returnState
="error_input"; 
 452     remainder
="$argSeconds" ; # seconds 
 453     ## Calculate full years Y, update remainder 
 454     fullYears
=$(( remainder / (365*24*60*60) )); 
 455     remainder
=$(( remainder - (fullYears*365*24*60*60) )); 
 456     ## Calculate full months M, update remainder 
 457     fullMonths
=$(( remainder / (30*24*60*60) )); 
 458     remainder
=$(( remainder - (fullMonths*30*24*60*60) )); 
 459     ## Calculate full days D, update remainder 
 460     fullDays
=$(( remainder / (24*60*60) )); 
 461     remainder
=$(( remainder - (fullDays*24*60*60) )); 
 462     ## Calculate full hours H, update remainder 
 463     fullHours
=$(( remainder / (60*60) )); 
 464     remainder
=$(( remainder - (fullHours*60*60) )); 
 465     ## Calculate full minutes M, update remainder 
 466     fullMinutes
=$(( remainder / (60) )); 
 467     remainder
=$(( remainder - (fullMinutes*60) )); 
 468     ## Calculate full seconds S, update remainder 
 469     fullSeconds
=$(( remainder / (1) )); 
 470     remainder
=$(( remainder - (remainder*1) )); 
 471     ## Check which fields filled 
 472     if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi 
 473     if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi 
 474     if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi 
 475     if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi 
 476     if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi 
 477     if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi 
 479     ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) 
 480     witherPrecision
="false" 
 483     if $hasYears && [[ $precision -gt 0 ]]; then 
 485         witherPrecision
="true"; 
 487         displayYears
="false"; 
 489     if $witherPrecision; then ((precision
--)); fi; 
 492     if $hasMonths && [[ $precision -gt 0 ]]; then 
 493         displayMonths
="true"; 
 494         witherPrecision
="true"; 
 496         displayMonths
="false"; 
 498     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 499         displayMonths
="true"; 
 501     if $witherPrecision; then ((precision
--)); fi; 
 504     if $hasDays && [[ $precision -gt 0 ]]; then 
 506         witherPrecision
="true"; 
 510     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 513     if $witherPrecision; then ((precision
--)); fi; 
 516     if $hasHours && [[ $precision -gt 0 ]]; then 
 518         witherPrecision
="true"; 
 520         displayHours
="false"; 
 522     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 525     if $witherPrecision; then ((precision
--)); fi; 
 528     if $hasMinutes && [[ $precision -gt 0 ]]; then 
 529         displayMinutes
="true"; 
 530         witherPrecision
="true"; 
 532         displayMinutes
="false"; 
 534     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 535         displayMinutes
="true"; 
 537     if $witherPrecision; then ((precision
--)); fi; 
 541     if $hasSeconds && [[ $precision -gt 0 ]]; then 
 542         displaySeconds
="true"; 
 543         witherPrecision
="true"; 
 545         displaySeconds
="false"; 
 547     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 548         displaySeconds
="true"; 
 550     if $witherPrecision; then ((precision
--)); fi; 
 552     ## Determine whether or not the "T" separator is needed to separate date and time elements 
 553     if ( $displayHours || $displayMinutes || $displaySeconds); then 
 554         displayDateTime
="true"; else displayDateTime
="false"; fi 
 556     ## Construct duration output string 
 558     if $displayYears; then 
 559         OUTPUT
=$OUTPUT$fullYears"Y"; fi 
 560     if $displayMonths; then 
 561         OUTPUT
=$OUTPUT$fullMonths"M"; fi 
 562     if $displayDays; then 
 563         OUTPUT
=$OUTPUT$fullDays"D"; fi 
 564     if $displayDateTime; then 
 565         OUTPUT
=$OUTPUT"T"; fi 
 566     if $displayHours; then 
 567         OUTPUT
=$OUTPUT$fullHours"H"; fi 
 568     if $displayMinutes; then 
 569         OUTPUT
=$OUTPUT$fullMinutes"M"; fi 
 570     if $displaySeconds; then 
 571         OUTPUT
=$OUTPUT$fullSeconds"S"; fi 
 573     ## Output duration string to stdout 
 574     echo "$OUTPUT" && returnState
="true"; 
 576     #===Determine function return code=== 
 577     if [ "$returnState" = "true" ]; then 
 579     elif [ "$returnState" = "error_input" ]; then 
 583         yell 
"ERROR:Unknown"; 
 587 } # Get duration (ex: PT10M4S ) 
 589     # Desc: Displays missing apps, files, and dirs 
 590     # Usage: displayMissing 
 591     # Input: associative arrays: appRollCall, fileRollCall, dirRollCall 
 592     # Output: stderr messages 
 593     #==BEGIN Display errors== 
 594     #===BEGIN Display Missing Apps=== 
 595     missingApps
="Missing apps  :" 
 596     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
 597     for key 
in "${!appRollCall[@]}"; do 
 598         value
="${appRollCall[$key]}" 
 599         if [ "$value" = "false" ]; then 
 600             #echo "DEBUG:Missing apps: $key => $value"; 
 601             missingApps
="$missingApps""$key " 
 605     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 606         echo "$missingApps" 1>&2; 
 608     #===END Display Missing Apps=== 
 610     #===BEGIN Display Missing Files=== 
 611     missingFiles
="Missing files:" 
 612     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done 
 613     for key 
in "${!fileRollCall[@]}"; do 
 614         value
="${fileRollCall[$key]}" 
 615         if [ "$value" = "false" ]; then 
 616             #echo "DEBUG:Missing files: $key => $value"; 
 617             missingFiles
="$missingFiles""$key " 
 621     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 622         echo "$missingFiles" 1>&2; 
 624     #===END Display Missing Files=== 
 626     #===BEGIN Display Missing Directories=== 
 627     missingDirs
="Missing dirs:" 
 628     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done 
 629     for key 
in "${!dirRollCall[@]}"; do 
 630         value
="${dirRollCall[$key]}" 
 631         if [ "$value" = "false" ]; then 
 632             #echo "DEBUG:Missing dirs: $key => $value"; 
 633             missingDirs
="$missingDirs""$key " 
 637     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 638         echo "$missingDirs" 1>&2; 
 640     #===END Display Missing Directories=== 
 642     #==END Display errors== 
 643 } # Display missing apps, files, dirs 
 645     #Desc: Sets script TTL 
 646     #Usage: setScriptTTL arg1 
 647     #Input: arg1: "day" or "hour" 
 649     #Depends: timeUntilNextHour or timeUntilNextDay 
 652     if [[ "$ARG1" = "day" ]]; then 
 653             # Set script lifespan to end at start of next day 
 654         if ! scriptTTL
="$(timeUntilNextDay)"; then 
 655             if [[ "$scriptTTL" -eq 0 ]]; then 
 656             ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 658             yell 
"ERROR: timeUntilNextDay exit code $?"; exit 1; 
 661     elif [[ "$ARG1" = "hour" ]]; then 
 662         # Set script lifespan to end at start of next hour 
 663         if ! scriptTTL
="$(timeUntilNextHour)"; then 
 664             if [[ "$scriptTTL" -eq 0 ]]; then 
 665                 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 667                 yell 
"ERROR: timeUntilNextHour exit code $?"; exit 1; 
 671         yell 
"ERROR:Invalid argument for setScriptTTL function."; exit 1; 
 673 } # Seconds until next (day|hour). 
 675     # Desc: Checks that a valid tar archive exists, creates one otherwise 
 676     # Usage: checkMakeTar [ path ] 
 678     # Input: arg1: path of tar archive 
 679     # Output: exit code 0 : tar readable 
 680     #         exit code 1 : tar missing; created 
 681     #         exit code 2 : tar not readable; moved; replaced 
 682     # Depends: try, tar, date 
 683     local PATH_TAR returnFlag0 returnFlag1 returnFlag2
 
 686     # Check if file is a valid tar archive 
 687     if tar --list --file="$PATH_TAR" 1>/dev
/null 
2>&1; then 
 688         ## T1: return success 
 689         returnFlag0
="tar valid"; 
 691         ## F1: Check if file exists 
 692         if [[ -f "$PATH_TAR" ]]; then 
 694             try 
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
 
 695                 returnFlag1
="tar moved"; 
 700         ## F2: Create tar archive, return 0 
 701         try 
tar --create --file="$PATH_TAR" --files-from=/dev
/null 
&& \
 
 702             returnFlag2
="tar created"; 
 705     # Determine function return code 
 706     if [[ "$returnFlag0" = "tar valid" ]]; then 
 708     elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then 
 709         return 1; # tar missing so created 
 710     elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then 
 711         return 2; # tar not readable so moved; replaced 
 713 } # checks if arg1 is tar; creates one otherwise 
 715     # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar 
 716     # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 718     # Input: arg1: data to be written 
 719     #        arg2: file name of file to be inserted into tar 
 720     #        arg3: tar archive path (must exist first) 
 721     #        arg4: temporary working dir 
 722     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 723     # Output: file written to disk 
 724     # Example: decrypt multiple large files in parallel 
 725     #          appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 726     #          appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 727     #          appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 729     # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 
 732     local FN
="${FUNCNAME[0]}"; 
 733     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 736     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 738     # Check tar path is a file 
 739     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 742     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 744     # Set command strings 
 745     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 746     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 747     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 748     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 754     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 755     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 756     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 757     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 758     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 759     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 760     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 761     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 763     # Write to temporary working dir 
 764     eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME"; 
 767     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 768     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 769 } # Append Bash var to file appended to Tar archive 
 771     # Desc: Processes first file and then appends to tar 
 772     # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 774     # Input: arg1: path of file to be (processed and) written 
 775     #        arg2: name to use for file inserted into tar 
 776     #        arg3: tar archive path (must exist first) 
 777     #        arg4: temporary working dir 
 778     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 779     # Output: file written to disk 
 780     # Example: decrypt multiple large files in parallel 
 781     #          appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 782     #          appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 783     #          appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 787     local FN
="${FUNCNAME[0]}"; 
 788     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 791     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 792     # Check tar path is a file 
 793     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 795     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 796     # Set command strings 
 797     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 798     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 799     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 800     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 802     # Input command string 
 806     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 807     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 808     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 809     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 810     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 811     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 812     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 813     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 815     # Write to temporary working dir 
 816     eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME"; 
 819     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 820     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 821 } # Append file to Tar archive 
 823     # Desc: Validates Input 
 824     # Usage: validateInput [str input] [str input type] 
 826     # Input: arg1: string to validate 
 827     #        arg2: string specifying input type (ex:"ssh_pubkey") 
 828     # Output: return code 0: if input string matched specified string type 
 829     # Depends: bash 5, yell 
 832     local FN
="${FUNCNAME[0]}"; 
 837     if [[ $# -gt 2 ]]; then yell 
"ERROR:$0:$FN:Too many arguments."; exit 1; fi; 
 840     if [[ -z "$argInput" ]]; then return 1; fi 
 844     ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") 
 845     if [[ "$argType" = "ssh_pubkey" ]]; then 
 846         if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\ ]*[[:alnum
:]+/=]*$ 
]]; then 
 850     ### Check for age1[:bech32:] 
 851     if [[ "$argType" = "age_pubkey" ]]; then 
 852         if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$ 
]]; then 
 855     # Return error if no condition matched. 
 857 } # Validates strings 
 858 magicWriteVersion
() { 
 859     # Desc: Appends time-stamped VERSION to PATHOUT_TAR 
 860     # Usage: magicWriteVersion 
 862     # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP 
 863     # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME 
 864     # Output: appends tar PATHOUT_TAR 
 865     # Depends: dateTimeShort, appendArgTar 
 866     local CONTENT_VERSION pubKeyIndex
 
 868     # Set VERSION file name 
 869     FILEOUT_VERSION
="$(dateTimeShort)..VERSION"; 
 871     # Gather VERSION data in CONTENT_VERSION 
 872     CONTENT_VERSION
="SCRIPT_VERSION=$SCRIPT_VERSION"; 
 873     #CONTENT_VERSION="$CONTENT_VERSION""\\n"; 
 874     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME"; 
 875     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL"; 
 876     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION"; 
 877     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL"; 
 878     CONTENT_VERSION
="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)"; 
 879     CONTENT_VERSION
="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME"; 
 880     ## Add list of recipient pubkeys 
 881     for pubkey 
in "${recPubKeysValid[@]}"; do 
 883         CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey"; 
 885     ## Process newline escapes 
 886     CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")" 
 888     # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR 
 889     appendArgTar 
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP"; 
 891 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar() 
 892 magicGatherWriteBuffer
() { 
 893     # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR 
 894     # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP, 
 895     # Inputs: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX 
 896     # Depends: yell, try, vbm, appendArgTar, tar 
 897     local FN
="${FUNCNAME[0]}"; 
 898     wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) 
 899     # Create buffer file with unique name 
 900     PATHOUT_BUFFER
="$DIR_TMP/buffer$SECONDS"; 
 902     timeout 
"$BUFFER_TTL"s gpspipe 
-r -o "$PATHOUT_BUFFER" ; 
 903     timeBufferStart
="$(dateTimeShort  "$(date --date="$BUFFER_TTL seconds ago")")"; # Note start time 
 904     vbm "DEBUG
:STATUS
:$FN:Started magicWriteBuffer
().
"; 
 905     # Determine file paths (time is start of buffer period) 
 906     FILEOUT_BASENAME="$timeBufferStart""--""$bufferTTL_STR""..
""$SCRIPT_HOSTNAME""_location
" && vbm "STATUS
:Set FILEOUT_BASENAME to
:$FILEOUT_BASENAME"; 
 907     ## Files saved to DIR_TMP 
 908     FILEOUT_NMEA="$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_NMEA to
:$FILEOUT_NMEA"; 
 909     FILEOUT_GPX="$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_GPX to
:$FILEOUT_GPX"; 
 910     FILEOUT_KML="$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_KML to
:$FILEOUT_KML"; 
 911     PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_NMEA" && vbm "STATUS
:Set PATHOUT_NMEA to
:$PATHOUT_NMEA"; 
 912     PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_GPX" && vbm "STATUS
:Set PATHOUT_GPX to
:$PATHOUT_GPX"; 
 913     PATHOUT_KML="$DIR_TMP"/"$FILEOUT_KML" && vbm "STATUS
:Set PATHOUT_KML to
:$PATHOUT_KML"; 
 914     ## Files saved to disk (DIR_OUT) 
 915     ### one file per day (Ex: "20200731..hostname_location.
[.gpx.gz
].
tar") 
 916     PATHOUT_TAR="$DIR_OUT"/"$(dateShort "$(date --date="$BUFFER_TTL seconds ago")")"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
 917         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
 919     vbm 
"STATUS:DIR_TMP     :$DIR_TMP"; 
 920     vbm 
"STATUS:PATHOUT_TAR :$PATHOUT_TAR"; 
 921     vbm 
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA"; 
 922     vbm 
"STATUS:PATHOUT_GPX:$PATHOUT_GPX"; 
 923     vbm 
"STATUS:PATHOUT_KML:$PATHOUT_KML"; 
 926     # Validate PATHOUT_TAR as tar. 
 927     checkMakeTar 
"$PATHOUT_TAR"; 
 928     ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) 
 929     if [[ $? 
-eq 1 ]] || [[ $? 
-eq 2 ]]; then magicWriteVersion
; fi 
 931     # Write bufferBash to PATHOUT_TAR 
 932     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data 
 933     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file 
 934     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file 
 936     # Remove secured chunks from DIR_TMP 
 937     rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML"; 
 938     vbm 
"DEBUG:STATUS:$FN:Finished magicWriteBuffer()."; 
 939 } # write buffer to disk 
 942     processArguments 
"$@" # Process arguments. 
 944     # Determine working directory 
 945     ## Set DIR_TMP_PARENT to user-specified value if specified 
 946     if [[ "$OPTION_TMPDIR" = "true" ]]; then 
 947             if [[ -d "$TMP_DIR_PRIORITY" ]]; then 
 948             DIR_TMP_PARENT
="$OPTION_TMPDIR";  
 950             yell 
"WARNING:Specified temporary working directory not valid:$OPTION_TMPDIR"; 
 955     ## Set DIR_TMP_PARENT to default or fallback otherwise 
 956     if [[ -d "$DIR_TMP_DEFAULT" ]]; then 
 957         DIR_TMP_PARENT
="$DIR_TMP_DEFAULT"; 
 958     elif [[ -d /tmp 
]]; then 
 959         yell 
"WARNING:/dev/shm not available. Falling back to /tmp ."; 
 960         DIR_TMP_PARENT
="/tmp"; 
 962         yell 
"ERROR:No valid working directory available. Exiting."; 
 966     ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START) 
 967     DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm 
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main(). 
 969     # Set output encryption and compression option strings 
 970     if [[ "$OPTION_ENCRYPT" = "true" ]]; then # Check if encryption option active. 
 971         if checkapp age
; then # Check that age is available. 
 972             for pubkey 
in "${recPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message 
 973                 vbm 
"DEBUG:Testing pubkey string:$pubkey"; 
 974                 if echo "butts" | age 
-a -r "$pubkey" 1>/dev
/null 
&& 
 975                         ( validateInput 
"$pubkey" "ssh_pubkey" || validateInput 
"$pubkey" "age_pubkey"); then 
 976                     #### Form age recipient string 
 977                     recipients
="$recipients""-r '$pubkey' "; 
 978                     vbm 
"STATUS:Added pubkey for forming age recipient string:""$pubkey"; 
 979                     vbm 
"DEBUG:recipients:""$recipients"; 
 980                     #### Add validated pubkey to recPubKeysValid array 
 981                     recPubKeysValid
+=("$pubkey") && vbm 
"DEBUG:recPubkeysValid:pubkey added:$pubkey"; 
 983                     yell 
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1; 
 986             vbm 
"DEBUG:Finished processing recPubKeys array"; 
 988             ##  Form age command string 
 989             CMD_ENCRYPT
="age ""$recipients " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
 990             CMD_ENCRYPT_SUFFIX
=".age" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
 992             yell 
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1; 
 995         CMD_ENCRYPT
="tee /dev/null " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
 996         CMD_ENCRYPT_SUFFIX
="" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
 997         vbm 
"DEBUG:Encryption not enabled." 
 999     if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active 
1000         if checkapp 
gzip; then # Check if gzip available 
1001             CMD_COMPRESS
="gzip " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1002             CMD_COMPRESS_SUFFIX
=".gz" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1004             yell 
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1; 
1007         CMD_COMPRESS
="tee /dev/null " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1008         CMD_COMPRESS_SUFFIX
="" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1009         vbm 
"DEBUG:Compression not enabled."; 
1012     # Check that critical apps and dirs are available, displag missing ones. 
1013     if ! checkapp gpspipe 
tar && ! checkdir 
"$DIR_OUT" "/dev/shm"; then 
1014         yell 
"ERROR:Critical components missing."; 
1015         displayMissing
; yell 
"Exiting."; exit 1; fi 
1017     # Set script lifespan 
1018     setScriptTTL 
"$SCRIPT_TTL"; # seconds until next new SCRIPT_TTL (ex: "day" or "hour") 
1020     # File name substring: encoded bufferTTL 
1021     bufferTTL_STR
="$(timeDuration $BUFFER_TTL)"; 
1023     # Init temp working dir 
1024     try 
mkdir "$DIR_TMP" && vbm 
"DEBUG:Working dir creatd at:$DIR_TMP"; 
1026     # Initialize 'tar' archive 
1027     ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar")) 
1028     PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
1029         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
1030     ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise. 
1031     checkMakeTar 
"$PATHOUT_TAR" && vbm 
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR"; 
1032     ## Append VERSION file to PATHOUT_TAR 
1035     # Define GPS conversion commands 
1036     CMD_CONV_NMEA
="tee /dev/null " && vbm 
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough 
1037     CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm 
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX 
1038     CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm 
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML 
1040     # MAIN LOOP:Record gps data until script lifespan ends 
1041     while [[ "$SECONDS" -lt "$scriptTTL" ]]; do 
1042         magicGatherWriteBuffer 
& 
1043         sleep "$BUFFER_TTL"; 
1048     try 
rm -r "$DIR_TMP"; 
1050     vbm 
"STATUS:Main function finished."; 
1052 #===END Declare local script functions=== 
1053 #==END Define script parameters== 
1056 #==BEGIN Perform work and exit== 
1057 main 
"$@" # Run main function. 
1059 #==END Perform work and exit==