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 argRecPubKeys 
# for processArguments function 
  28 ## Initialize variables 
  29 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
=""; 
  31 #===BEGIN Declare local script functions=== 
  33     # Desc: If arg is a command, save result in assoc array 'appRollCall' 
  34     # Usage: checkapp arg1 arg2 arg3 ... 
  35     # Input: global assoc. array 'appRollCall' 
  36     # Output: adds/updates key(value) to global assoc array 'appRollCall' 
  38     #echo "DEBUG:$(date +%S.%N)..Starting checkapp function." 
  39     #echo "DEBUG:args: $@" 
  40     #echo "DEBUG:returnState:$returnState" 
  44         #echo "DEBUG:processing arg:$arg" 
  45         if command -v "$arg" 1>/dev
/null 
2>&1; then # Check if arg is a valid command 
  46             appRollCall
[$arg]="true"; 
  47             #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]} 
  48             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  50             appRollCall
[$arg]="false"; returnState
="false"; 
  54     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
  55     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
  57     #===Determine function return code=== 
  58     if [ "$returnState" = "true" ]; then 
  59         #echo "DEBUG:checkapp returns true for $arg"; 
  62         #echo "DEBUG:checkapp returns false for $arg"; 
  65 } # Check that app exists 
  67     # Desc: If arg is a file path, save result in assoc array 'fileRollCall' 
  68     # Usage: checkfile arg1 arg2 arg3 ... 
  69     # Input: global assoc. array 'fileRollCall' 
  70     # Output: adds/updates key(value) to global assoc array 'fileRollCall'; 
  71     # Output: returns 0 if app found, 1 otherwise 
  76         #echo "DEBUG:processing arg:$arg" 
  77         if [ -f "$arg" ]; then 
  78             fileRollCall
["$arg"]="true"; 
  79             #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]} 
  80             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
  82             fileRollCall
["$arg"]="false"; returnState
="false"; 
  86     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done 
  87     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
  89     #===Determine function return code=== 
  90     if [ "$returnState" = "true" ]; then 
  91         #echo "DEBUG:checkapp returns true for $arg"; 
  94         #echo "DEBUG:checkapp returns false for $arg"; 
  97 } # Check that file exists 
  99     # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' 
 100     # Usage: checkdir arg1 arg2 arg3 ... 
 101     # Input: global assoc. array 'dirRollCall' 
 102     # Output: adds/updates key(value) to global assoc array 'dirRollCall'; 
 103     # Output: returns 0 if app found, 1 otherwise 
 108         #echo "DEBUG:processing arg:$arg" 
 109         if [ -d "$arg" ]; then 
 110             dirRollCall
["$arg"]="true"; 
 111             #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]} 
 112             if ! [ "$returnState" = "false" ]; then returnState
="true"; fi 
 113         elif [ "$arg" = "" ]; then 
 114             dirRollCall
["$arg"]="false"; returnState
="false"; 
 120     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done 
 121     #echo "DEBUG:evaluating returnstate. returnState:"$returnState 
 123     #===Determine function return code=== 
 124     if [ "$returnState" = "true" ]; then 
 125         #echo "DEBUG:checkapp returns true for $arg"; 
 128         #echo "DEBUG:checkapp returns false for $arg"; 
 131 } # Check that dir exists 
 133 # Yell, Die, Try Three-Fingered Claw technique 
 134 # Ref/Attrib: https://stackoverflow.com/a/25515370 
 135 yell
() { echo "$0: $*" >&2; } 
 136 die
() { yell 
"$*"; exit 111; } 
 137 try
() { "$@" || die 
"cannot $*"; } 
 140     echo "$@" 1>&2; # Define stderr echo function. 
 141 } # Define stderr message function. 
 144     echoerr 
"    bkgpslog [ options ]" 
 147     echoerr 
"    -h, --help" 
 148     echoerr 
"            Display help information." 
 150     echoerr 
"            Display script version." 
 151     echoerr 
"    -v, --verbose" 
 152     echoerr 
"            Display debugging info." 
 153     echoerr 
"    -e, --encrypt" 
 154     echoerr 
"            Encrypt output." 
 155     echoerr 
"    -r, --recipient [ pubkey string ]" 
 156     echoerr 
"            Specify recipient. May be age or ssh pubkey." 
 157     echoerr 
"            See https://github.com/FiloSottile/age" 
 158     echoerr 
"    -o, --output [ directory ]" 
 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" 
 165     echoerr 
"            Specify parent directory for temporary working directory." 
 166     echoerr 
"            Default: \"/dev/shm\"" 
 167     echoerr 
"    -R, --recipient-dir" 
 168     echoerr 
"            Specify directory containing files whose first lines are" 
 169     echoerr 
"            to be interpreted as pubkey strings (see \'-r\' option)." 
 171     echoerr 
"EXAMPLE: (bash script lines)" 
 172     echoerr 
"/bin/bash bkgpslog -v -e -c \\" 
 173     echoerr 
"-z \"UTC\" -t \"/dev/shm\" \\" 
 174     echoerr 
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\" 
 175     echoerr 
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\" 
 176     echoerr 
"-o ~/Sync/Location" 
 177 } # Display information on how to use this script. 
 179     echoerr 
"$SCRIPT_VERSION" 
 180 } # Display script version. 
 182     # Usage: vbm "DEBUG:verbose message here" 
 183     # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true". 
 185     #   - OPTION_VERBOSE  variable set by processArguments function. (ex: "true", "false") 
 186     #   - "$@"            positional arguments fed to this function. 
 188     # Script function dependencies: echoerr 
 189     # External function dependencies: echo 
 190     # Last modified: 2020-04-11T23:57Z 
 191     # Last modified by: Steven Baltakatei Sandoval 
 195     if [ "$OPTION_VERBOSE" = "true" ]; then 
 196         FUNCTION_TIME
=$(date --iso-8601=ns); # Save current time in nano seconds. 
 197         echoerr 
"[$FUNCTION_TIME] ""$*"; # Display argument text. 
 201     return 0; # Function finished. 
 202 } # Verbose message display function. 
 204     while [ ! $# -eq 0 ]; do   # While number of arguments ($#) is not (!) equal to (-eq) zero (0). 
 205         #echoerr "DEBUG:Starting processArguments while loop." 
 206         #echoerr "DEBUG:Provided arguments are:""$*" 
 208             -h | --help) showUsage
; exit 1;; # Display usage. 
 209             --version) showVersion
; exit 1;; # Show version 
 210             -v | --verbose) OPTION_VERBOSE
="true"; vbm 
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. 
 211             -o | --output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm 
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory. 
 212             -e | --encrypt) OPTION_ENCRYPT
="true"; vbm 
"DEBUG:Encrypted output mode enabled.";; # Enable encryption 
 213             -r | --recipient) OPTION_RECIPIENTS
="true"; argRecPubKeys
+=("$2"); vbm 
"STATUS:pubkey added:""$2"; shift;; # Add recipients 
 214             -c | --compress) OPTION_COMPRESS
="true"; vbm 
"DEBUG:Compressed output mode enabled.";; # Enable compression 
 215             -z | --time-zone) try setTimeZoneEV 
"$2"; shift;; # Set timestamp timezone 
 216             -t | --temp-dir) OPTION_TMPDIR
="true" && argTempDirPriority
="$2"; shift;; # Set time zone 
 217             -R | --recipient-dir) OPTION_RECIPIENTS
="true"; OPTION_RECDIR
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir 
 218             *) echoerr 
"ERROR: Unrecognized argument: $1"; echoerr 
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. 
 222 } # Argument Processing 
 224     # Desc: Set time zone environment variable TZ 
 225     # Usage: setTimeZoneEV arg1 
 226     # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") 
 227     #        TZDIR env var (optional; default: "/usr/share/zoneinfo") 
 229     #         exit code 0 on success 
 230     #         exit code 1 on incorrect number of arguments 
 231     #         exit code 2 if unable to validate arg1 
 232     # Depends: yell, printenv, bash 5 
 233     # Tested on: Debian 10 
 235     local tzDir returnState
 
 236     if ! [[ $# -eq 1 ]]; then 
 237         yell 
"ERROR:Invalid argument count."; 
 241     # Read TZDIR env var if available 
 242     if printenv TZDIR 
1>/dev
/null 
2>&1; then 
 243         tzDir
="$(printenv TZDIR)"; 
 245         tzDir
="/usr/share/zoneinfo"; 
 249     if ! [[ -f "$tzDir"/"$ARG1" ]]; then 
 250         yell 
"ERROR:Invalid time zone argument."; 
 253     # Export ARG1 as TZ environment variable 
 254         TZ
="$ARG1" && export TZ 
&& returnState
="true"; 
 257     # Determine function return code 
 258     if [ "$returnState" = "true" ]; then 
 261 } # Exports TZ environment variable 
 263     # Desc: Report seconds until next day. 
 265     # Output: stdout: integer seconds until next day 
 266     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 267     # Usage: timeUntilNextDay 
 268     # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi 
 269     # Depends: date 8, echo 8, yell, try 
 271     local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
 
 273     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 274     TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. 
 275     SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second). 
 276     if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then 
 278     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then 
 279         returnState
="warning_zero"; 
 280         yell 
"WARNING:Reported time until next day exactly zero."; 
 281     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then 
 282         returnState
="warning_negative"; 
 283         yell 
"WARNING:Reported time until next day is negative."; 
 286     try 
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report 
 288     # Determine function return code 
 289     if [[ "$returnState" = "true" ]]; then 
 291     elif [[ "$returnState" = "warning_zero" ]]; then 
 293     elif [[ "$returnState" = "warning_negative" ]]; then 
 296 } # Report seconds until next day 
 298     # Desc: Report seconds until next hour 
 300     # Output: stdout: integer seconds until next hour 
 301     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 302     # Usage: timeUntilNextHour 
 303     # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi 
 305     local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
 
 306     TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 307     TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. 
 308     SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second). 
 309     if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then 
 311     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then 
 312         returnState
="warning_zero"; 
 313         yell 
"WARNING:Reported time until next hour exactly zero."; 
 314     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then 
 315         returnState
="warning_negative"; 
 316         yell 
"WARNING:Reported time until next hour is negative."; 
 319     try 
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report 
 321     # Determine function return code 
 322     if [[ "$returnState" = "true" ]]; then 
 324     elif [[ "$returnState" = "warning_zero" ]]; then 
 326     elif [[ "$returnState" = "warning_negative" ]]; then 
 329 } # Report seconds until next hour 
 331     # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) 
 332     # Usage: dateTimeShort ([str date]) 
 334     # Input: arg1: 'date'-parsable timestamp string (optional) 
 335     # Output: stdout: timestamp (ISO-8601, no separators) 
 337     local TIME_CURRENT TIME_CURRENT_SHORT
 
 341     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 342     # Decide to parse current or supplied date 
 343     ## Check if time argument empty 
 344     if [[ -z "$argTime" ]]; then 
 345         ## T: Time argument empty, use current time 
 346         TIME_INPUT
="$TIME_CURRENT"; 
 348         ## F: Time argument exists, validate time 
 349         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 350             ### T: Time argument is valid; use it 
 351             TIME_INPUT
="$argTime"; 
 353             ### F: Time argument not valid; exit 
 354             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 357     # Construct and deliver separator-les date string 
 358     TIME_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)"; 
 359     echo "$TIME_CURRENT_SHORT"; 
 360 } # Get YYYYmmddTHHMMSS±zzzz 
 362     # Desc: Date without separators (YYYYmmdd) 
 363     # Usage: dateShort ([str date]) 
 365     # Input: arg1: 'date'-parsable timestamp string (optional) 
 366     # Output: stdout: date (ISO-8601, no separators) 
 368     local TIME_CURRENT DATE_CURRENT_SHORT
 
 372     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 373     # Decide to parse current or supplied date 
 374     ## Check if time argument empty 
 375     if [[ -z "$argTime" ]]; then 
 376         ## T: Time argument empty, use current time 
 377         TIME_INPUT
="$TIME_CURRENT"; 
 379         ## F: Time argument exists, validate time 
 380         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 381             ### T: Time argument is valid; use it 
 382             TIME_INPUT
="$argTime"; 
 384             ### F: Time argument not valid; exit 
 385             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 388     # Construct and deliver separator-les date string     
 389     DATE_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. 
 390     echo "$DATE_CURRENT_SHORT"; 
 393     # Desc: Given seconds, output ISO-8601 duration string 
 394     # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information 
 395     # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) 
 396     # Usage: timeDuration [1:seconds] ([2:precision]) 
 398     # Input: arg1: seconds as base 10 integer >= 0  (ex: 3601) 
 399     #        arg2: precision level (optional; default=2) 
 400     # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") 
 401     #         exit code 0: success 
 402     #         exit code 1: error_input 
 403     #         exit code 2: error_unknown 
 404     # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' 
 405     # Depends: date 8 (gnucoreutils), yell,  
 406     local returnState argSeconds argPrecision remainder precision witherPrecision
 
 407     local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
 
 408     local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
 
 409     local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
 
 411     argSeconds
="$1"; # read arg1 (seconds) 
 412     argPrecision
="$2"; # read arg2 (precision) 
 413     precision
=2; # set default precision 
 415     # Check that between one and two arguments is supplied 
 416     if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then 
 417         yell 
"ERROR:Invalid number of arguments:$# . Exiting."; 
 418         returnState
="error_input"; fi 
 420     # Check that argSeconds provided 
 421     if [[ $# -ge 1 ]]; then 
 422         ## Check that argSeconds is a positive integer 
 423         if [[ "$argSeconds" =~ ^
[[:digit
:]]+$ 
]]; then 
 426             yell 
"ERROR:argSeconds not a digit."; 
 427             returnState
="error_input"; 
 430         yell 
"ERROR:No argument provided. Exiting."; 
 434     # Consider whether argPrecision was provided 
 435     if  [[ $# -eq 2 ]]; then 
 436         # Check that argPrecision is a positive integer 
 437         if [[ "$argPrecision" =~ ^
[[:digit
:]]+$ 
]] && [[ "$argPrecision" -gt 0 ]]; then 
 438         precision
="$argPrecision"; 
 440             yell 
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; 
 441             returnState
="error_input"; 
 447     remainder
="$argSeconds" ; # seconds 
 448     ## Calculate full years Y, update remainder 
 449     fullYears
=$(( remainder / (365*24*60*60) )); 
 450     remainder
=$(( remainder - (fullYears*365*24*60*60) )); 
 451     ## Calculate full months M, update remainder 
 452     fullMonths
=$(( remainder / (30*24*60*60) )); 
 453     remainder
=$(( remainder - (fullMonths*30*24*60*60) )); 
 454     ## Calculate full days D, update remainder 
 455     fullDays
=$(( remainder / (24*60*60) )); 
 456     remainder
=$(( remainder - (fullDays*24*60*60) )); 
 457     ## Calculate full hours H, update remainder 
 458     fullHours
=$(( remainder / (60*60) )); 
 459     remainder
=$(( remainder - (fullHours*60*60) )); 
 460     ## Calculate full minutes M, update remainder 
 461     fullMinutes
=$(( remainder / (60) )); 
 462     remainder
=$(( remainder - (fullMinutes*60) )); 
 463     ## Calculate full seconds S, update remainder 
 464     fullSeconds
=$(( remainder / (1) )); 
 465     remainder
=$(( remainder - (remainder*1) )); 
 466     ## Check which fields filled 
 467     if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi 
 468     if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi 
 469     if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi 
 470     if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi 
 471     if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi 
 472     if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi 
 474     ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) 
 475     witherPrecision
="false" 
 478     if $hasYears && [[ $precision -gt 0 ]]; then 
 480         witherPrecision
="true"; 
 482         displayYears
="false"; 
 484     if $witherPrecision; then ((precision
--)); fi; 
 487     if $hasMonths && [[ $precision -gt 0 ]]; then 
 488         displayMonths
="true"; 
 489         witherPrecision
="true"; 
 491         displayMonths
="false"; 
 493     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 494         displayMonths
="true"; 
 496     if $witherPrecision; then ((precision
--)); fi; 
 499     if $hasDays && [[ $precision -gt 0 ]]; then 
 501         witherPrecision
="true"; 
 505     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 508     if $witherPrecision; then ((precision
--)); fi; 
 511     if $hasHours && [[ $precision -gt 0 ]]; then 
 513         witherPrecision
="true"; 
 515         displayHours
="false"; 
 517     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 520     if $witherPrecision; then ((precision
--)); fi; 
 523     if $hasMinutes && [[ $precision -gt 0 ]]; then 
 524         displayMinutes
="true"; 
 525         witherPrecision
="true"; 
 527         displayMinutes
="false"; 
 529     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 530         displayMinutes
="true"; 
 532     if $witherPrecision; then ((precision
--)); fi; 
 536     if $hasSeconds && [[ $precision -gt 0 ]]; then 
 537         displaySeconds
="true"; 
 538         witherPrecision
="true"; 
 540         displaySeconds
="false"; 
 542     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 543         displaySeconds
="true"; 
 545     if $witherPrecision; then ((precision
--)); fi; 
 547     ## Determine whether or not the "T" separator is needed to separate date and time elements 
 548     if ( $displayHours || $displayMinutes || $displaySeconds); then 
 549         displayDateTime
="true"; else displayDateTime
="false"; fi 
 551     ## Construct duration output string 
 553     if $displayYears; then 
 554         OUTPUT
=$OUTPUT$fullYears"Y"; fi 
 555     if $displayMonths; then 
 556         OUTPUT
=$OUTPUT$fullMonths"M"; fi 
 557     if $displayDays; then 
 558         OUTPUT
=$OUTPUT$fullDays"D"; fi 
 559     if $displayDateTime; then 
 560         OUTPUT
=$OUTPUT"T"; fi 
 561     if $displayHours; then 
 562         OUTPUT
=$OUTPUT$fullHours"H"; fi 
 563     if $displayMinutes; then 
 564         OUTPUT
=$OUTPUT$fullMinutes"M"; fi 
 565     if $displaySeconds; then 
 566         OUTPUT
=$OUTPUT$fullSeconds"S"; fi 
 568     ## Output duration string to stdout 
 569     echo "$OUTPUT" && returnState
="true"; 
 571     #===Determine function return code=== 
 572     if [ "$returnState" = "true" ]; then 
 574     elif [ "$returnState" = "error_input" ]; then 
 578         yell 
"ERROR:Unknown"; 
 582 } # Get duration (ex: PT10M4S ) 
 584     # Desc: Displays missing apps, files, and dirs 
 585     # Usage: displayMissing 
 586     # Input: associative arrays: appRollCall, fileRollCall, dirRollCall 
 587     # Output: stderr messages 
 588     #==BEGIN Display errors== 
 589     #===BEGIN Display Missing Apps=== 
 590     missingApps
="Missing apps  :" 
 591     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
 592     for key 
in "${!appRollCall[@]}"; do 
 593         value
="${appRollCall[$key]}" 
 594         if [ "$value" = "false" ]; then 
 595             #echo "DEBUG:Missing apps: $key => $value"; 
 596             missingApps
="$missingApps""$key " 
 600     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 601         echo "$missingApps" 1>&2; 
 603     #===END Display Missing Apps=== 
 605     #===BEGIN Display Missing Files=== 
 606     missingFiles
="Missing files:" 
 607     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done 
 608     for key 
in "${!fileRollCall[@]}"; do 
 609         value
="${fileRollCall[$key]}" 
 610         if [ "$value" = "false" ]; then 
 611             #echo "DEBUG:Missing files: $key => $value"; 
 612             missingFiles
="$missingFiles""$key " 
 616     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 617         echo "$missingFiles" 1>&2; 
 619     #===END Display Missing Files=== 
 621     #===BEGIN Display Missing Directories=== 
 622     missingDirs
="Missing dirs:" 
 623     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done 
 624     for key 
in "${!dirRollCall[@]}"; do 
 625         value
="${dirRollCall[$key]}" 
 626         if [ "$value" = "false" ]; then 
 627             #echo "DEBUG:Missing dirs: $key => $value"; 
 628             missingDirs
="$missingDirs""$key " 
 632     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 633         echo "$missingDirs" 1>&2; 
 635     #===END Display Missing Directories=== 
 637     #==END Display errors== 
 638 } # Display missing apps, files, dirs 
 640     #Desc: Sets script TTL 
 641     #Usage: setScriptTTL arg1 
 642     #Input: arg1: "day" or "hour" 
 644     #Depends: timeUntilNextHour or timeUntilNextDay 
 647     if [[ "$ARG1" = "day" ]]; then 
 648             # Set script lifespan to end at start of next day 
 649         if ! scriptTTL
="$(timeUntilNextDay)"; then 
 650             if [[ "$scriptTTL" -eq 0 ]]; then 
 651             ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 653             yell 
"ERROR: timeUntilNextDay exit code $?"; exit 1; 
 656     elif [[ "$ARG1" = "hour" ]]; then 
 657         # Set script lifespan to end at start of next hour 
 658         if ! scriptTTL
="$(timeUntilNextHour)"; then 
 659             if [[ "$scriptTTL" -eq 0 ]]; then 
 660                 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 662                 yell 
"ERROR: timeUntilNextHour exit code $?"; exit 1; 
 666         yell 
"ERROR:Invalid argument for setScriptTTL function."; exit 1; 
 668 } # Seconds until next (day|hour). 
 670     # Desc: Checks that a valid tar archive exists, creates one otherwise 
 671     # Usage: checkMakeTar [ path ] 
 673     # Input: arg1: path of tar archive 
 674     # Output: exit code 0 : tar readable 
 675     #         exit code 1 : tar missing; created 
 676     #         exit code 2 : tar not readable; moved; replaced 
 677     # Depends: try, tar, date 
 678     local PATH_TAR returnFlag0 returnFlag1 returnFlag2
 
 681     # Check if file is a valid tar archive 
 682     if tar --list --file="$PATH_TAR" 1>/dev
/null 
2>&1; then 
 683         ## T1: return success 
 684         returnFlag0
="tar valid"; 
 686         ## F1: Check if file exists 
 687         if [[ -f "$PATH_TAR" ]]; then 
 689             try 
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
 
 690                 returnFlag1
="tar moved"; 
 695         ## F2: Create tar archive, return 0 
 696         try 
tar --create --file="$PATH_TAR" --files-from=/dev
/null 
&& \
 
 697             returnFlag2
="tar created"; 
 700     # Determine function return code 
 701     if [[ "$returnFlag0" = "tar valid" ]]; then 
 703     elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then 
 704         return 1; # tar missing so created 
 705     elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then 
 706         return 2; # tar not readable so moved; replaced 
 708 } # checks if arg1 is tar; creates one otherwise 
 710     # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar 
 711     # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 713     # Input: arg1: data to be written 
 714     #        arg2: file name of file to be inserted into tar 
 715     #        arg3: tar archive path (must exist first) 
 716     #        arg4: temporary working dir 
 717     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 718     # Output: file written to disk 
 719     # Example: decrypt multiple large files in parallel 
 720     #          appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 721     #          appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 722     #          appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 724     # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 
 727     local FN
="${FUNCNAME[0]}"; 
 728     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 731     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 733     # Check tar path is a file 
 734     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 737     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 739     # Set command strings 
 740     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 741     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 742     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 743     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 749     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 750     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 751     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 752     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 753     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 754     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 755     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 756     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 758     # Write to temporary working dir 
 759     eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME"; 
 762     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 763     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 764 } # Append Bash var to file appended to Tar archive 
 766     # Desc: Processes first file and then appends to tar 
 767     # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 769     # Input: arg1: path of file to be (processed and) written 
 770     #        arg2: name to use for file inserted into tar 
 771     #        arg3: tar archive path (must exist first) 
 772     #        arg4: temporary working dir 
 773     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 774     # Output: file written to disk 
 775     # Example: decrypt multiple large files in parallel 
 776     #          appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 777     #          appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 778     #          appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 782     local FN
="${FUNCNAME[0]}"; 
 783     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 786     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 787     # Check tar path is a file 
 788     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 790     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 791     # Set command strings 
 792     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 793     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 794     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 795     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 797     # Input command string 
 801     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 802     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 803     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 804     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 805     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 806     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 807     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 808     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 810     # Write to temporary working dir 
 811     eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME"; 
 814     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 815     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 816 } # Append file to Tar archive 
 818     # Desc: Checks if string is an age-compatible pubkey 
 819     # Usage: checkAgePubkey [str pubkey] 
 821     # Input: arg1: string 
 822     # Output: return code 0: string is age-compatible pubkey 
 823     #         return code 1: string is NOT an age-compatible pubkey 
 824     #         age stderr (ex: there is stderr if invalid string provided) 
 825     # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 ) 
 829     if echo "test" | age 
-a -r "$argPubkey" 1>/dev
/null
; then 
 836     # Desc: Validates Input 
 837     # Usage: validateInput [str input] [str input type] 
 839     # Input: arg1: string to validate 
 840     #        arg2: string specifying input type (ex:"ssh_pubkey") 
 841     # Output: return code 0: if input string matched specified string type 
 842     # Depends: bash 5, yell 
 845     local FN
="${FUNCNAME[0]}"; 
 850     if [[ $# -gt 2 ]]; then yell 
"ERROR:$0:$FN:Too many arguments."; exit 1; fi; 
 853     if [[ -z "$argInput" ]]; then return 1; fi 
 857     ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") 
 858     if [[ "$argType" = "ssh_pubkey" ]]; then 
 859         if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\ ]*[[:alnum
:]+/=]*$ 
]]; then 
 863     ### Check for age1[:bech32:] 
 864     if [[ "$argType" = "age_pubkey" ]]; then 
 865         if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$ 
]]; then 
 868     # Return error if no condition matched. 
 870 } # Validates strings 
 871 magicWriteVersion
() { 
 872     # Desc: Appends time-stamped VERSION to PATHOUT_TAR 
 873     # Usage: magicWriteVersion 
 875     # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP 
 876     # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME 
 877     # Output: appends tar PATHOUT_TAR 
 878     # Depends: dateTimeShort, appendArgTar 
 879     local CONTENT_VERSION pubKeyIndex
 
 881     # Set VERSION file name 
 882     FILEOUT_VERSION
="$(dateTimeShort)..VERSION"; 
 884     # Gather VERSION data in CONTENT_VERSION 
 885     CONTENT_VERSION
="SCRIPT_VERSION=$SCRIPT_VERSION"; 
 886     #CONTENT_VERSION="$CONTENT_VERSION""\\n"; 
 887     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME"; 
 888     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL"; 
 889     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION"; 
 890     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL"; 
 891     CONTENT_VERSION
="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)"; 
 892     CONTENT_VERSION
="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME"; 
 893     ## Add list of recipient pubkeys 
 894     for pubkey 
in "${recPubKeysValid[@]}"; do 
 896         CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey"; 
 898     ## Process newline escapes 
 899     CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")" 
 901     # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR 
 902     appendArgTar 
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP"; 
 904 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar() 
 905 magicGatherWriteBuffer
() { 
 906     # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR 
 907     # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP, 
 908     # Inputs: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX 
 909     # Depends: yell, try, vbm, appendArgTar, tar 
 910     local FN
="${FUNCNAME[0]}"; 
 911     wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) 
 912     # Create buffer file with unique name 
 913     PATHOUT_BUFFER
="$DIR_TMP/buffer$SECONDS"; 
 915     timeout 
"$BUFFER_TTL"s gpspipe 
-r -o "$PATHOUT_BUFFER" ; 
 916     timeBufferStart
="$(dateTimeShort  "$(date --date="$BUFFER_TTL seconds ago")")"; # Note start time 
 917     vbm "DEBUG
:STATUS
:$FN:Started magicWriteBuffer
().
"; 
 918     # Determine file paths (time is start of buffer period) 
 919     FILEOUT_BASENAME="$timeBufferStart""--""$bufferTTL_STR""..
""$SCRIPT_HOSTNAME""_location
" && vbm "STATUS
:Set FILEOUT_BASENAME to
:$FILEOUT_BASENAME"; 
 920     ## Files saved to DIR_TMP 
 921     FILEOUT_NMEA="$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_NMEA to
:$FILEOUT_NMEA"; 
 922     FILEOUT_GPX="$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_GPX to
:$FILEOUT_GPX"; 
 923     FILEOUT_KML="$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_KML to
:$FILEOUT_KML"; 
 924     PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_NMEA" && vbm "STATUS
:Set PATHOUT_NMEA to
:$PATHOUT_NMEA"; 
 925     PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_GPX" && vbm "STATUS
:Set PATHOUT_GPX to
:$PATHOUT_GPX"; 
 926     PATHOUT_KML="$DIR_TMP"/"$FILEOUT_KML" && vbm "STATUS
:Set PATHOUT_KML to
:$PATHOUT_KML"; 
 927     ## Files saved to disk (DIR_OUT) 
 928     ### one file per day (Ex: "20200731..hostname_location.
[.gpx.gz
].
tar") 
 929     PATHOUT_TAR="$DIR_OUT"/"$(dateShort "$(date --date="$BUFFER_TTL seconds ago")")"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
 930         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
 932     vbm 
"STATUS:DIR_TMP     :$DIR_TMP"; 
 933     vbm 
"STATUS:PATHOUT_TAR :$PATHOUT_TAR"; 
 934     vbm 
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA"; 
 935     vbm 
"STATUS:PATHOUT_GPX:$PATHOUT_GPX"; 
 936     vbm 
"STATUS:PATHOUT_KML:$PATHOUT_KML"; 
 939     # Validate PATHOUT_TAR as tar. 
 940     checkMakeTar 
"$PATHOUT_TAR"; 
 941     ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) 
 942     if [[ $? 
-eq 1 ]] || [[ $? 
-eq 2 ]]; then magicWriteVersion
; fi 
 944     # Write bufferBash to PATHOUT_TAR 
 945     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data 
 946     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file 
 947     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file 
 949     # Remove secured chunks from DIR_TMP 
 950     rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML"; 
 951     vbm 
"DEBUG:STATUS:$FN:Finished magicWriteBuffer()."; 
 952 } # write buffer to disk 
 953 magicParseRecipientDir
() { 
 954     # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory") 
 955     # Inputs:  vars: OPTION_RECDIR, argRecDir, OPTION_ENCRYPTION 
 956     #          arry: recPubKeysValid 
 957     # Outputs: arry: recPubKeysValid 
 958     # Depends: processArguments, 
 959     local recFileLine updateRecipients recipientDir
 
 960     declare -a candRecPubKeysValid
 
 962     # Check that '-e' and '-R' set 
 963     if [[ "$OPTION_ENCRYPTION" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then 
 964         ### Check that argRecDir is a directory. 
 965         if [[ -d "$argRecDir" ]]; then 
 966             recipientDir
="$argRecDir"; 
 967             #### Initialize variable indicating outcome of pubkey review 
 968             unset updateRecipients
 
 969             #### Add existing recipients 
 970             candRecPubKeysValid
=(${recPubKeysValid[@]}); 
 971             #### Parse files in recipientDir 
 972             for file in "$recipientDir"/*; do 
 973                 ##### Read first line of each file 
 974                 recFileLine
="$(cat "$file" | head -n1)"; 
 975                 ##### check if first line is a valid pubkey 
 976                 if checkAgePubkey 
"$recFileLine" && \
 
 977                         ( validateInput 
"$recFileLine" "ssh_pubkey" || validateInput 
"$recFileLine" "age_pubkey"); then 
 978                     ###### T: add candidate pubkey to candRecPubKeysValid 
 979                     candRecPubKeysValid
+=("$recFileLine"); 
 981                     ###### F: throw warning; 
 982                     yell 
"ERROR:Invalid recipient file detected. Not modifying recipient list." 
 983                     updateRecipients
="false"; 
 986             #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected 
 987             if ! updateRecipients
="false"; then 
 988                 recPubKeysValid
=(${candRecPubKeysValid[@]}); 
 991             yell 
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1; 
 994     # Handle case if '-e' set but '-R' not set 
 995     if [[ "$OPTION_ENCRYPTION" = "true" ]] && [[ ! "$OPTION_RECDIR" = "true" ]]; then 
 996         yell 
"ERROR: \'-e\' set but \'-R\' is not set."; fi; 
 997     # Handle case if '-R' set but '-e' not set 
 998     if [[ ! "$OPTION_ENCRYPTION" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then 
 999         yell 
"ERROR: \'-R\' is set but \'-e\' is not set."; fi; 
1000 } # Update recPubKeysValid with argRecDir 
1001 magicParseRecipientArgs
() { 
1002     # Desc: Parses recipient arguments specified by '-r' option 
1003     # Input:  vars: OPTION_ENCRYPT from processArguments() 
1004     #         arry: argRecPubKeys from processArguments() 
1005     # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX 
1006     #         arry: recPubKeysValid 
1007     # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments() 
1010     # Check if encryption option active. 
1011     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then  
1012         if checkapp age
; then # Check that age is available. 
1013             for pubkey 
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message 
1014                 vbm 
"DEBUG:Testing pubkey string:$pubkey"; 
1015                 if checkAgePubkey 
"$pubkey" && \
 
1016                         ( validateInput 
"$pubkey" "ssh_pubkey" || validateInput 
"$pubkey" "age_pubkey"); then 
1017                     #### Form age recipient string 
1018                     recipients
="$recipients""-r '$pubkey' "; 
1019                     vbm 
"STATUS:Added pubkey for forming age recipient string:""$pubkey"; 
1020                     vbm 
"DEBUG:recipients:""$recipients"; 
1021                     #### Add validated pubkey to recPubKeysValid array 
1022                     recPubKeysValid
+=("$pubkey") && vbm 
"DEBUG:recPubkeysValid:pubkey added:$pubkey"; 
1024                     yell 
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1; 
1027             vbm 
"DEBUG:Finished processing argRecPubKeys array"; 
1029             ##  Form age command string 
1030             CMD_ENCRYPT
="age ""$recipients " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
1031             CMD_ENCRYPT_SUFFIX
=".age" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
1033             yell 
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1; 
1036         CMD_ENCRYPT
="tee /dev/null " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
1037         CMD_ENCRYPT_SUFFIX
="" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
1038         vbm 
"DEBUG:Encryption not enabled." 
1040     # Catch case if '-e' is set but '-r' or '-R' is not 
1041     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECIPIENTS" = "true" ]]; then 
1042         yell 
"ERROR:\'-e\' set but no \'-r\' or \'-R\' set."; exit 1; fi; 
1043     # Catch case if '-r' or '-R' set but '-e' is not 
1044     if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then 
1045         yell 
"ERROR:\'-r\' or \'-R\' set but \'-e\' is not set."; exit 1; fi;  
1046 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix 
1047 magicParseCompressionArg
() { 
1048     # Desc: Parses compression arguments specified by '-c' option 
1049     # Input:  vars: OPTION_COMPRESS 
1050     # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX 
1051     # Depends: checkapp(), vbm(), gzip,  
1052     if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active 
1053         if checkapp 
gzip; then # Check if gzip available 
1054             CMD_COMPRESS
="gzip " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1055             CMD_COMPRESS_SUFFIX
=".gz" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1057             yell 
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1; 
1060         CMD_COMPRESS
="tee /dev/null " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1061         CMD_COMPRESS_SUFFIX
="" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1062         vbm 
"DEBUG:Compression not enabled."; 
1064 } # Form compression cmd string and filename suffix 
1065 magicInitWorkingDir
() { 
1066     # Desc: Determine temporary working directory from defaults or user input 
1067     # Usage: magicInitWorkignDir 
1068     # Input:  vars: OPTION_TEMPDIR, argTempDirPriority, DIR_TMP_DEFAULT 
1069     # Input:  vars: SCRIPT_TIME_START 
1070     # Output: vars: DIR_TMP 
1071     # Depends: processArguments(), vbm(), yell() 
1072     # Parse '-t' option (user-specified temporary working dir) 
1073     ## Set DIR_TMP_PARENT to user-specified value if specified 
1074     local DIR_TMP_PARENT
 
1076     if [[ "$OPTION_TMPDIR" = "true" ]]; then 
1077         if [[ -d "$argTempDirPriority" ]]; then 
1078             DIR_TMP_PARENT
="$argTempDirPriority";  
1080             yell 
"WARNING:Specified temporary working directory not valid:$argTempDirPriority"; 
1081             exit 1; # Exit since user requires a specific temp dir and it is not available. 
1084     ## Set DIR_TMP_PARENT to default or fallback otherwise 
1085         if [[ -d "$DIR_TMP_DEFAULT" ]]; then 
1086             DIR_TMP_PARENT
="$DIR_TMP_DEFAULT"; 
1087         elif [[ -d /tmp 
]]; then 
1088             yell 
"WARNING:$DIR_TMP_DEFAULT not available. Falling back to /tmp ."; 
1089             DIR_TMP_PARENT
="/tmp"; 
1091             yell 
"ERROR:No valid working directory available. Exiting."; 
1095     ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START) 
1096     DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm 
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().     
1097 } # Sets working dir 
1101     processArguments 
"$@"; 
1102     ## Act upon arguments 
1103     ### Determine working directory 
1104     magicInitWorkingDir
; # Sets DIR_TMP from argTempDirPriority 
1105     ### Set output encryption and compression option strings 
1106     #### React to "-r" ("encryption recipients") option 
1107     magicParseRecipientArgs
; # Updates recPubKeysValid, CMD_ENCRYPT[_SUFFIX] 
1108     #### React to "-c" ("compression") option 
1109     magicParseCompressionArg
; # Updates CMD_COMPRESS[_SUFFIX] 
1110     #### React to "-R" ("recipient directory") option 
1111     magicParseRecipientDir
; # Updates recPubKeysValid 
1113     # Check that critical apps and dirs are available, display missing ones. 
1114     if ! checkapp gpspipe 
tar && ! checkdir 
"$DIR_OUT" "DIR_TMP"; then 
1115         yell 
"ERROR:Critical components missing."; 
1116         displayMissing
; yell 
"Exiting."; exit 1; fi 
1118     # Set script lifespan 
1119     setScriptTTL 
"$SCRIPT_TTL"; # seconds until next new SCRIPT_TTL (ex: "day" or "hour") 
1121     # File name substring: encoded bufferTTL 
1122     bufferTTL_STR
="$(timeDuration $BUFFER_TTL)"; 
1124     # Init temp working dir 
1125     try 
mkdir "$DIR_TMP" && vbm 
"DEBUG:Working dir creatd at:$DIR_TMP"; 
1127     # Initialize 'tar' archive 
1128     ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar")) 
1129     PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
1130         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
1131     ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise. 
1132     checkMakeTar 
"$PATHOUT_TAR" && vbm 
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR"; 
1133     ## Append VERSION file to PATHOUT_TAR 
1136     # Define GPS conversion commands 
1137     CMD_CONV_NMEA
="tee /dev/null " && vbm 
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough 
1138     CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm 
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX 
1139     CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm 
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML 
1141     # MAIN LOOP:Record gps data until script lifespan ends 
1142     while [[ "$SECONDS" -lt "$scriptTTL" ]]; do 
1143         magicGatherWriteBuffer 
& 
1144         sleep "$BUFFER_TTL"; 
1149     try 
rm -r "$DIR_TMP"; 
1151     vbm 
"STATUS:Main function finished."; 
1153 #===END Declare local script functions=== 
1154 #==END Define script parameters== 
1157 #==BEGIN Perform work and exit== 
1158 main 
"$@" # Run main function. 
1160 #==END Perform work and exit==