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.";; 
 213             -r | --recipient) # Add 'age' recipient via public key string 
 214                 argRecPubKeys
+=("$2"); vbm 
"STATUS:pubkey added:""$2"; shift;; 
 215             -c | --compress) OPTION_COMPRESS
="true"; vbm 
"DEBUG:Compressed output mode enabled.";; 
 216             -z | --time-zone) try setTimeZoneEV 
"$2"; shift;; 
 217             -t | --temp-dir) OPTION_TMPDIR
="true" && argTmpDirPriority
="$2"; shift;; 
 218             -R | --recipient-dir) OPTION_RECDIR
="true" && argRecDir
="$2"; shift;; 
 219             *) echoerr 
"ERROR: Unrecognized argument: $1"; echoerr 
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. 
 223 } # Argument Processing 
 225     # Desc: Set time zone environment variable TZ 
 226     # Usage: setTimeZoneEV arg1 
 227     # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") 
 228     #        TZDIR env var (optional; default: "/usr/share/zoneinfo") 
 230     #         exit code 0 on success 
 231     #         exit code 1 on incorrect number of arguments 
 232     #         exit code 2 if unable to validate arg1 
 233     # Depends: yell, printenv, bash 5 
 234     # Tested on: Debian 10 
 236     local tzDir returnState
 
 237     if ! [[ $# -eq 1 ]]; then 
 238         yell 
"ERROR:Invalid argument count."; 
 242     # Read TZDIR env var if available 
 243     if printenv TZDIR 
1>/dev
/null 
2>&1; then 
 244         tzDir
="$(printenv TZDIR)"; 
 246         tzDir
="/usr/share/zoneinfo"; 
 250     if ! [[ -f "$tzDir"/"$ARG1" ]]; then 
 251         yell 
"ERROR:Invalid time zone argument."; 
 254     # Export ARG1 as TZ environment variable 
 255         TZ
="$ARG1" && export TZ 
&& returnState
="true"; 
 258     # Determine function return code 
 259     if [ "$returnState" = "true" ]; then 
 262 } # Exports TZ environment variable 
 264     # Desc: Report seconds until next day. 
 266     # Output: stdout: integer seconds until next day 
 267     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 268     # Usage: timeUntilNextDay 
 269     # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi 
 270     # Depends: date 8, echo 8, yell, try 
 272     local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
 
 274     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 275     TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. 
 276     SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second). 
 277     if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then 
 279     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then 
 280         returnState
="warning_zero"; 
 281         yell 
"WARNING:Reported time until next day exactly zero."; 
 282     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then 
 283         returnState
="warning_negative"; 
 284         yell 
"WARNING:Reported time until next day is negative."; 
 287     try 
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report 
 289     # Determine function return code 
 290     if [[ "$returnState" = "true" ]]; then 
 292     elif [[ "$returnState" = "warning_zero" ]]; then 
 294     elif [[ "$returnState" = "warning_negative" ]]; then 
 297 } # Report seconds until next day 
 299     # Desc: Report seconds until next hour 
 301     # Output: stdout: integer seconds until next hour 
 302     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 303     # Usage: timeUntilNextHour 
 304     # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi 
 306     local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
 
 307     TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 308     TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. 
 309     SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second). 
 310     if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then 
 312     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then 
 313         returnState
="warning_zero"; 
 314         yell 
"WARNING:Reported time until next hour exactly zero."; 
 315     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then 
 316         returnState
="warning_negative"; 
 317         yell 
"WARNING:Reported time until next hour is negative."; 
 320     try 
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report 
 322     # Determine function return code 
 323     if [[ "$returnState" = "true" ]]; then 
 325     elif [[ "$returnState" = "warning_zero" ]]; then 
 327     elif [[ "$returnState" = "warning_negative" ]]; then 
 330 } # Report seconds until next hour 
 332     # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) 
 333     # Usage: dateTimeShort ([str date]) 
 335     # Input: arg1: 'date'-parsable timestamp string (optional) 
 336     # Output: stdout: timestamp (ISO-8601, no separators) 
 338     local TIME_CURRENT TIME_CURRENT_SHORT
 
 342     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 343     # Decide to parse current or supplied date 
 344     ## Check if time argument empty 
 345     if [[ -z "$argTime" ]]; then 
 346         ## T: Time argument empty, use current time 
 347         TIME_INPUT
="$TIME_CURRENT"; 
 349         ## F: Time argument exists, validate time 
 350         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 351             ### T: Time argument is valid; use it 
 352             TIME_INPUT
="$argTime"; 
 354             ### F: Time argument not valid; exit 
 355             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 358     # Construct and deliver separator-les date string 
 359     TIME_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)"; 
 360     echo "$TIME_CURRENT_SHORT"; 
 361 } # Get YYYYmmddTHHMMSS±zzzz 
 363     # Desc: Date without separators (YYYYmmdd) 
 364     # Usage: dateShort ([str date]) 
 366     # Input: arg1: 'date'-parsable timestamp string (optional) 
 367     # Output: stdout: date (ISO-8601, no separators) 
 369     local TIME_CURRENT DATE_CURRENT_SHORT
 
 373     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 374     # Decide to parse current or supplied date 
 375     ## Check if time argument empty 
 376     if [[ -z "$argTime" ]]; then 
 377         ## T: Time argument empty, use current time 
 378         TIME_INPUT
="$TIME_CURRENT"; 
 380         ## F: Time argument exists, validate time 
 381         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 382             ### T: Time argument is valid; use it 
 383             TIME_INPUT
="$argTime"; 
 385             ### F: Time argument not valid; exit 
 386             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 389     # Construct and deliver separator-les date string     
 390     DATE_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. 
 391     echo "$DATE_CURRENT_SHORT"; 
 394     # Desc: Given seconds, output ISO-8601 duration string 
 395     # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information 
 396     # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) 
 397     # Usage: timeDuration [1:seconds] ([2:precision]) 
 399     # Input: arg1: seconds as base 10 integer >= 0  (ex: 3601) 
 400     #        arg2: precision level (optional; default=2) 
 401     # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") 
 402     #         exit code 0: success 
 403     #         exit code 1: error_input 
 404     #         exit code 2: error_unknown 
 405     # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' 
 406     # Depends: date 8 (gnucoreutils), yell,  
 407     local returnState argSeconds argPrecision remainder precision witherPrecision
 
 408     local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
 
 409     local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
 
 410     local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
 
 412     argSeconds
="$1"; # read arg1 (seconds) 
 413     argPrecision
="$2"; # read arg2 (precision) 
 414     precision
=2; # set default precision 
 416     # Check that between one and two arguments is supplied 
 417     if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then 
 418         yell 
"ERROR:Invalid number of arguments:$# . Exiting."; 
 419         returnState
="error_input"; fi 
 421     # Check that argSeconds provided 
 422     if [[ $# -ge 1 ]]; then 
 423         ## Check that argSeconds is a positive integer 
 424         if [[ "$argSeconds" =~ ^
[[:digit
:]]+$ 
]]; then 
 427             yell 
"ERROR:argSeconds not a digit."; 
 428             returnState
="error_input"; 
 431         yell 
"ERROR:No argument provided. Exiting."; 
 435     # Consider whether argPrecision was provided 
 436     if  [[ $# -eq 2 ]]; then 
 437         # Check that argPrecision is a positive integer 
 438         if [[ "$argPrecision" =~ ^
[[:digit
:]]+$ 
]] && [[ "$argPrecision" -gt 0 ]]; then 
 439         precision
="$argPrecision"; 
 441             yell 
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; 
 442             returnState
="error_input"; 
 448     remainder
="$argSeconds" ; # seconds 
 449     ## Calculate full years Y, update remainder 
 450     fullYears
=$(( remainder / (365*24*60*60) )); 
 451     remainder
=$(( remainder - (fullYears*365*24*60*60) )); 
 452     ## Calculate full months M, update remainder 
 453     fullMonths
=$(( remainder / (30*24*60*60) )); 
 454     remainder
=$(( remainder - (fullMonths*30*24*60*60) )); 
 455     ## Calculate full days D, update remainder 
 456     fullDays
=$(( remainder / (24*60*60) )); 
 457     remainder
=$(( remainder - (fullDays*24*60*60) )); 
 458     ## Calculate full hours H, update remainder 
 459     fullHours
=$(( remainder / (60*60) )); 
 460     remainder
=$(( remainder - (fullHours*60*60) )); 
 461     ## Calculate full minutes M, update remainder 
 462     fullMinutes
=$(( remainder / (60) )); 
 463     remainder
=$(( remainder - (fullMinutes*60) )); 
 464     ## Calculate full seconds S, update remainder 
 465     fullSeconds
=$(( remainder / (1) )); 
 466     remainder
=$(( remainder - (remainder*1) )); 
 467     ## Check which fields filled 
 468     if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi 
 469     if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi 
 470     if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi 
 471     if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi 
 472     if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi 
 473     if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi 
 475     ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) 
 476     witherPrecision
="false" 
 479     if $hasYears && [[ $precision -gt 0 ]]; then 
 481         witherPrecision
="true"; 
 483         displayYears
="false"; 
 485     if $witherPrecision; then ((precision
--)); fi; 
 488     if $hasMonths && [[ $precision -gt 0 ]]; then 
 489         displayMonths
="true"; 
 490         witherPrecision
="true"; 
 492         displayMonths
="false"; 
 494     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 495         displayMonths
="true"; 
 497     if $witherPrecision; then ((precision
--)); fi; 
 500     if $hasDays && [[ $precision -gt 0 ]]; then 
 502         witherPrecision
="true"; 
 506     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 509     if $witherPrecision; then ((precision
--)); fi; 
 512     if $hasHours && [[ $precision -gt 0 ]]; then 
 514         witherPrecision
="true"; 
 516         displayHours
="false"; 
 518     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 521     if $witherPrecision; then ((precision
--)); fi; 
 524     if $hasMinutes && [[ $precision -gt 0 ]]; then 
 525         displayMinutes
="true"; 
 526         witherPrecision
="true"; 
 528         displayMinutes
="false"; 
 530     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 531         displayMinutes
="true"; 
 533     if $witherPrecision; then ((precision
--)); fi; 
 537     if $hasSeconds && [[ $precision -gt 0 ]]; then 
 538         displaySeconds
="true"; 
 539         witherPrecision
="true"; 
 541         displaySeconds
="false"; 
 543     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 544         displaySeconds
="true"; 
 546     if $witherPrecision; then ((precision
--)); fi; 
 548     ## Determine whether or not the "T" separator is needed to separate date and time elements 
 549     if ( $displayHours || $displayMinutes || $displaySeconds); then 
 550         displayDateTime
="true"; else displayDateTime
="false"; fi 
 552     ## Construct duration output string 
 554     if $displayYears; then 
 555         OUTPUT
=$OUTPUT$fullYears"Y"; fi 
 556     if $displayMonths; then 
 557         OUTPUT
=$OUTPUT$fullMonths"M"; fi 
 558     if $displayDays; then 
 559         OUTPUT
=$OUTPUT$fullDays"D"; fi 
 560     if $displayDateTime; then 
 561         OUTPUT
=$OUTPUT"T"; fi 
 562     if $displayHours; then 
 563         OUTPUT
=$OUTPUT$fullHours"H"; fi 
 564     if $displayMinutes; then 
 565         OUTPUT
=$OUTPUT$fullMinutes"M"; fi 
 566     if $displaySeconds; then 
 567         OUTPUT
=$OUTPUT$fullSeconds"S"; fi 
 569     ## Output duration string to stdout 
 570     echo "$OUTPUT" && returnState
="true"; 
 572     #===Determine function return code=== 
 573     if [ "$returnState" = "true" ]; then 
 575     elif [ "$returnState" = "error_input" ]; then 
 579         yell 
"ERROR:Unknown"; 
 583 } # Get duration (ex: PT10M4S ) 
 585     # Desc: Displays missing apps, files, and dirs 
 586     # Usage: displayMissing 
 587     # Input: associative arrays: appRollCall, fileRollCall, dirRollCall 
 588     # Output: stderr messages 
 589     #==BEGIN Display errors== 
 590     #===BEGIN Display Missing Apps=== 
 591     missingApps
="Missing apps  :" 
 592     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
 593     for key 
in "${!appRollCall[@]}"; do 
 594         value
="${appRollCall[$key]}" 
 595         if [ "$value" = "false" ]; then 
 596             #echo "DEBUG:Missing apps: $key => $value"; 
 597             missingApps
="$missingApps""$key " 
 601     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 602         echo "$missingApps" 1>&2; 
 604     #===END Display Missing Apps=== 
 606     #===BEGIN Display Missing Files=== 
 607     missingFiles
="Missing files:" 
 608     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done 
 609     for key 
in "${!fileRollCall[@]}"; do 
 610         value
="${fileRollCall[$key]}" 
 611         if [ "$value" = "false" ]; then 
 612             #echo "DEBUG:Missing files: $key => $value"; 
 613             missingFiles
="$missingFiles""$key " 
 617     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 618         echo "$missingFiles" 1>&2; 
 620     #===END Display Missing Files=== 
 622     #===BEGIN Display Missing Directories=== 
 623     missingDirs
="Missing dirs:" 
 624     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done 
 625     for key 
in "${!dirRollCall[@]}"; do 
 626         value
="${dirRollCall[$key]}" 
 627         if [ "$value" = "false" ]; then 
 628             #echo "DEBUG:Missing dirs: $key => $value"; 
 629             missingDirs
="$missingDirs""$key " 
 633     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 634         echo "$missingDirs" 1>&2; 
 636     #===END Display Missing Directories=== 
 638     #==END Display errors== 
 639 } # Display missing apps, files, dirs 
 641     #Desc: Sets script TTL 
 642     #Usage: setScriptTTL arg1 
 643     #Input: arg1: "day" or "hour" 
 645     #Depends: timeUntilNextHour or timeUntilNextDay 
 648     if [[ "$ARG1" = "day" ]]; then 
 649             # Set script lifespan to end at start of next day 
 650         if ! scriptTTL
="$(timeUntilNextDay)"; then 
 651             if [[ "$scriptTTL" -eq 0 ]]; then 
 652             ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 654             yell 
"ERROR: timeUntilNextDay exit code $?"; exit 1; 
 657     elif [[ "$ARG1" = "hour" ]]; then 
 658         # Set script lifespan to end at start of next hour 
 659         if ! scriptTTL
="$(timeUntilNextHour)"; then 
 660             if [[ "$scriptTTL" -eq 0 ]]; then 
 661                 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 663                 yell 
"ERROR: timeUntilNextHour exit code $?"; exit 1; 
 667         yell 
"ERROR:Invalid argument for setScriptTTL function."; exit 1; 
 669 } # Seconds until next (day|hour). 
 671     # Desc: Checks that a valid tar archive exists, creates one otherwise 
 672     # Usage: checkMakeTar [ path ] 
 674     # Input: arg1: path of tar archive 
 675     # Output: exit code 0 : tar readable 
 676     #         exit code 1 : tar missing; created 
 677     #         exit code 2 : tar not readable; moved; replaced 
 678     # Depends: try, tar, date 
 679     local PATH_TAR returnFlag0 returnFlag1 returnFlag2
 
 682     # Check if file is a valid tar archive 
 683     if tar --list --file="$PATH_TAR" 1>/dev
/null 
2>&1; then 
 684         ## T1: return success 
 685         returnFlag0
="tar valid"; 
 687         ## F1: Check if file exists 
 688         if [[ -f "$PATH_TAR" ]]; then 
 690             try 
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
 
 691                 returnFlag1
="tar moved"; 
 696         ## F2: Create tar archive, return 0 
 697         try 
tar --create --file="$PATH_TAR" --files-from=/dev
/null 
&& \
 
 698             returnFlag2
="tar created"; 
 701     # Determine function return code 
 702     if [[ "$returnFlag0" = "tar valid" ]]; then 
 704     elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then 
 705         return 1; # tar missing so created 
 706     elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then 
 707         return 2; # tar not readable so moved; replaced 
 709 } # checks if arg1 is tar; creates one otherwise 
 711     # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar 
 712     # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 714     # Input: arg1: data to be written 
 715     #        arg2: file name of file to be inserted into tar 
 716     #        arg3: tar archive path (must exist first) 
 717     #        arg4: temporary working dir 
 718     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 719     # Output: file written to disk 
 720     # Example: decrypt multiple large files in parallel 
 721     #          appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 722     #          appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 723     #          appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 725     # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 
 728     local FN
="${FUNCNAME[0]}"; 
 729     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 732     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 734     # Check tar path is a file 
 735     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 738     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 740     # Set command strings 
 741     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 742     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 743     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 744     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 750     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 751     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 752     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 753     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 754     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 755     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 756     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 757     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 759     # Write to temporary working dir 
 760     eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME"; 
 763     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 764     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 765 } # Append Bash var to file appended to Tar archive 
 767     # Desc: Processes first file and then appends to tar 
 768     # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 770     # Input: arg1: path of file to be (processed and) written 
 771     #        arg2: name to use for file inserted into tar 
 772     #        arg3: tar archive path (must exist first) 
 773     #        arg4: temporary working dir 
 774     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 775     # Output: file written to disk 
 776     # Example: decrypt multiple large files in parallel 
 777     #          appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 778     #          appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 779     #          appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 783     local FN
="${FUNCNAME[0]}"; 
 784     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 787     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 788     # Check tar path is a file 
 789     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 791     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 792     # Set command strings 
 793     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 794     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 795     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 796     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 798     # Input command string 
 802     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 803     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 804     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 805     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 806     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 807     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 808     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 809     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 811     # Write to temporary working dir 
 812     eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME"; 
 815     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 816     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 817 } # Append file to Tar archive 
 819     # Desc: Checks if string is an age-compatible pubkey 
 820     # Usage: checkAgePubkey [str pubkey] 
 822     # Input: arg1: string 
 823     # Output: return code 0: string is age-compatible pubkey 
 824     #         return code 1: string is NOT an age-compatible pubkey 
 825     #         age stderr (ex: there is stderr if invalid string provided) 
 826     # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 ) 
 830     if echo "test" | age 
-a -r "$argPubkey" 1>/dev
/null
; then 
 837     # Desc: Validates Input 
 838     # Usage: validateInput [str input] [str input type] 
 840     # Input: arg1: string to validate 
 841     #        arg2: string specifying input type (ex:"ssh_pubkey") 
 842     # Output: return code 0: if input string matched specified string type 
 843     # Depends: bash 5, yell 
 846     local FN
="${FUNCNAME[0]}"; 
 851     if [[ $# -gt 2 ]]; then yell 
"ERROR:$0:$FN:Too many arguments."; exit 1; fi; 
 854     if [[ -z "$argInput" ]]; then return 1; fi 
 858     ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") 
 859     if [[ "$argType" = "ssh_pubkey" ]]; then 
 860         if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\ ]*[[:alnum
:]+/=]*$ 
]]; then 
 864     ### Check for age1[:bech32:] 
 865     if [[ "$argType" = "age_pubkey" ]]; then 
 866         if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$ 
]]; then 
 869     # Return error if no condition matched. 
 871 } # Validates strings 
 872 magicWriteVersion
() { 
 873     # Desc: Appends time-stamped VERSION to PATHOUT_TAR 
 874     # Usage: magicWriteVersion 
 876     # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP 
 877     # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME 
 878     # Output: appends tar PATHOUT_TAR 
 879     # Depends: dateTimeShort, appendArgTar 
 880     local CONTENT_VERSION pubKeyIndex
 
 882     # Set VERSION file name 
 883     FILEOUT_VERSION
="$(dateTimeShort)..VERSION"; 
 885     # Gather VERSION data in CONTENT_VERSION 
 886     CONTENT_VERSION
="SCRIPT_VERSION=$SCRIPT_VERSION"; 
 887     #CONTENT_VERSION="$CONTENT_VERSION""\\n"; 
 888     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME"; 
 889     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL"; 
 890     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION"; 
 891     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL"; 
 892     CONTENT_VERSION
="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)"; 
 893     CONTENT_VERSION
="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME"; 
 894     ## Add list of recipient pubkeys 
 895     for pubkey 
in "${recPubKeysValid[@]}"; do 
 897         CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey"; 
 899     ## Process newline escapes 
 900     CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")" 
 902     # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR 
 903     appendArgTar 
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP"; 
 905 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar() 
 906 magicGatherWriteBuffer
() { 
 907     # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR 
 908     # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP, 
 909     # Inputs: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX 
 910     # Depends: yell, try, vbm, appendArgTar, tar 
 911     local FN
="${FUNCNAME[0]}"; 
 912     wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) 
 913     # Create buffer file with unique name 
 914     PATHOUT_BUFFER
="$DIR_TMP/buffer$SECONDS"; 
 916     timeout 
"$BUFFER_TTL"s gpspipe 
-r -o "$PATHOUT_BUFFER" ; 
 917     timeBufferStart
="$(dateTimeShort  "$(date --date="$BUFFER_TTL seconds ago")")"; # Note start time 
 918     vbm "DEBUG
:STATUS
:$FN:Started magicWriteBuffer
().
"; 
 919     # Determine file paths (time is start of buffer period) 
 920     FILEOUT_BASENAME="$timeBufferStart""--""$bufferTTL_STR""..
""$SCRIPT_HOSTNAME""_location
" && vbm "STATUS
:Set FILEOUT_BASENAME to
:$FILEOUT_BASENAME"; 
 921     ## Files saved to DIR_TMP 
 922     FILEOUT_NMEA="$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_NMEA to
:$FILEOUT_NMEA"; 
 923     FILEOUT_GPX="$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_GPX to
:$FILEOUT_GPX"; 
 924     FILEOUT_KML="$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_KML to
:$FILEOUT_KML"; 
 925     PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_NMEA" && vbm "STATUS
:Set PATHOUT_NMEA to
:$PATHOUT_NMEA"; 
 926     PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_GPX" && vbm "STATUS
:Set PATHOUT_GPX to
:$PATHOUT_GPX"; 
 927     PATHOUT_KML="$DIR_TMP"/"$FILEOUT_KML" && vbm "STATUS
:Set PATHOUT_KML to
:$PATHOUT_KML"; 
 928     ## Files saved to disk (DIR_OUT) 
 929     ### one file per day (Ex: "20200731..hostname_location.
[.gpx.gz
].
tar") 
 930     PATHOUT_TAR="$DIR_OUT"/"$(dateShort "$(date --date="$BUFFER_TTL seconds ago")")"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
 931         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
 933     vbm 
"STATUS:DIR_TMP     :$DIR_TMP"; 
 934     vbm 
"STATUS:PATHOUT_TAR :$PATHOUT_TAR"; 
 935     vbm 
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA"; 
 936     vbm 
"STATUS:PATHOUT_GPX:$PATHOUT_GPX"; 
 937     vbm 
"STATUS:PATHOUT_KML:$PATHOUT_KML"; 
 940     # Validate PATHOUT_TAR as tar. 
 941     checkMakeTar 
"$PATHOUT_TAR"; 
 942     ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) 
 943     if [[ $? 
-eq 1 ]] || [[ $? 
-eq 2 ]]; then magicWriteVersion
; fi 
 945     # Write bufferBash to PATHOUT_TAR 
 946     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data 
 947     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file 
 948     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file 
 950     # Remove secured chunks from DIR_TMP 
 951     rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML"; 
 952     vbm 
"DEBUG:STATUS:$FN:Finished magicWriteBuffer()."; 
 953 } # write buffer to disk 
 954 magicParseRecipientDir
() { 
 955     # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory") 
 956     # Inputs: vars: OPTION_RECDIR, argRecDir,  
 957     #         arry: recPubKeysValid 
 958     # Outputs: arry: recPubKeysValid (modified with pubkeys in argRecDir if pubkeys valid) 
 959     # Depends: processArguments, 
 960     local recFileLine updateRecipients recipientDir
 
 961     declare -a candRecPubKeysValid
 
 963     if [[ "$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 } # Update recPubKeysValid with argRecDir 
 995 magicParseRecipientArgs
() { 
 996     # Desc: Parses recipient arguments specified by '-r' option 
 997     # Input:  vars: OPTION_ENCRYPT from processArguments() 
 998     #         arry: argRecPubKeys from processArguments() 
 999     # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX 
1000     #         arry: recPubKeysValid 
1001     # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments() 
1004     if [[ "$OPTION_ENCRYPT" = "true" ]]; then # Check if encryption option active. 
1005         if checkapp age
; then # Check that age is available. 
1006             for pubkey 
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message 
1007                 vbm 
"DEBUG:Testing pubkey string:$pubkey"; 
1008                 if checkAgePubkey 
"$pubkey" && \
 
1009                         ( validateInput 
"$pubkey" "ssh_pubkey" || validateInput 
"$pubkey" "age_pubkey"); then 
1010                     #### Form age recipient string 
1011                     recipients
="$recipients""-r '$pubkey' "; 
1012                     vbm 
"STATUS:Added pubkey for forming age recipient string:""$pubkey"; 
1013                     vbm 
"DEBUG:recipients:""$recipients"; 
1014                     #### Add validated pubkey to recPubKeysValid array 
1015                     recPubKeysValid
+=("$pubkey") && vbm 
"DEBUG:recPubkeysValid:pubkey added:$pubkey"; 
1017                     yell 
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1; 
1020             vbm 
"DEBUG:Finished processing argRecPubKeys array"; 
1022             ##  Form age command string 
1023             CMD_ENCRYPT
="age ""$recipients " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
1024             CMD_ENCRYPT_SUFFIX
=".age" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
1026             yell 
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1; 
1029         CMD_ENCRYPT
="tee /dev/null " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
1030         CMD_ENCRYPT_SUFFIX
="" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
1031         vbm 
"DEBUG:Encryption not enabled." 
1033 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix 
1034 magicParseCompressionArg
() { 
1035     # Desc: Parses compression arguments specified by '-c' option 
1036     # Input:  vars: OPTION_COMPRESS 
1037     # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX 
1038     # Depends: checkapp(), vbm(), gzip,  
1039     if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active 
1040         if checkapp 
gzip; then # Check if gzip available 
1041             CMD_COMPRESS
="gzip " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1042             CMD_COMPRESS_SUFFIX
=".gz" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1044             yell 
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1; 
1047         CMD_COMPRESS
="tee /dev/null " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1048         CMD_COMPRESS_SUFFIX
="" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1049         vbm 
"DEBUG:Compression not enabled."; 
1051 } # Form compression cmd string and filename suffix 
1052 magicInitWorkingDir
() { 
1053     # Desc: Determine temporary working directory from defaults or user input 
1054     # Input:  vars: OPTION_TEMPDIR, argTmpDirPriority, DIR_TMP_DEFAULT 
1055     # Input:  vars: SCRIPT_TIME_START 
1056     # Output: vars: DIR_TMP 
1057     # Depends: processArguments(), vbm(), yell() 
1058     # Parse '-t' option (user-specified temporary working dir) 
1059     ## Set DIR_TMP_PARENT to user-specified value if specified 
1060     local DIR_TMP_PARENT
 
1062     if [[ "$OPTION_TMPDIR" = "true" ]]; then 
1063         if [[ -d "$argTempDirPriority" ]]; then 
1064             DIR_TMP_PARENT
="$argTempDirPriority";  
1066             yell 
"WARNING:Specified temporary working directory not valid:$OPTION_TMPDIR"; 
1067             exit 1; # Exit since user requires a specific temp dir and it is not available. 
1070     ## Set DIR_TMP_PARENT to default or fallback otherwise 
1071         if [[ -d "$DIR_TMP_DEFAULT" ]]; then 
1072             DIR_TMP_PARENT
="$DIR_TMP_DEFAULT"; 
1073         elif [[ -d /tmp 
]]; then 
1074             yell 
"WARNING:$DIR_TMP_DEFAULT not available. Falling back to /tmp ."; 
1075             DIR_TMP_PARENT
="/tmp"; 
1077             yell 
"ERROR:No valid working directory available. Exiting."; 
1081     ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START) 
1082     DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm 
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().     
1083 } # Sets working dir 
1087     processArguments 
"$@"; 
1088     ## Act upon arguments 
1089     ### Determine working directory 
1090     magicInitWorkingDir
; 
1091     ### Set output encryption and compression option strings 
1092     #### React to "-r" ("encryption recipients") option 
1093     magicParseRecipientArgs
; 
1094     #### React to "-c" ("compression") option 
1095     magicParseCompressionArg
; 
1096     #### React to "-R" ("recipient directory") option 
1097     magicParseRecipientDir
; 
1099     # Check that critical apps and dirs are available, display missing ones. 
1100     if ! checkapp gpspipe 
tar && ! checkdir 
"$DIR_OUT" "DIR_TMP"; then 
1101         yell 
"ERROR:Critical components missing."; 
1102         displayMissing
; yell 
"Exiting."; exit 1; fi 
1104     # Set script lifespan 
1105     setScriptTTL 
"$SCRIPT_TTL"; # seconds until next new SCRIPT_TTL (ex: "day" or "hour") 
1107     # File name substring: encoded bufferTTL 
1108     bufferTTL_STR
="$(timeDuration $BUFFER_TTL)"; 
1110     # Init temp working dir 
1111     try 
mkdir "$DIR_TMP" && vbm 
"DEBUG:Working dir creatd at:$DIR_TMP"; 
1113     # Initialize 'tar' archive 
1114     ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar")) 
1115     PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
1116         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
1117     ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise. 
1118     checkMakeTar 
"$PATHOUT_TAR" && vbm 
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR"; 
1119     ## Append VERSION file to PATHOUT_TAR 
1122     # Define GPS conversion commands 
1123     CMD_CONV_NMEA
="tee /dev/null " && vbm 
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough 
1124     CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm 
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX 
1125     CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm 
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML 
1127     # MAIN LOOP:Record gps data until script lifespan ends 
1128     while [[ "$SECONDS" -lt "$scriptTTL" ]]; do 
1129         magicGatherWriteBuffer 
& 
1130         sleep "$BUFFER_TTL"; 
1135     try 
rm -r "$DIR_TMP"; 
1137     vbm 
"STATUS:Main function finished."; 
1139 #===END Declare local script functions=== 
1140 #==END Define script parameters== 
1143 #==BEGIN Perform work and exit== 
1144 main 
"$@" # Run main function. 
1146 #==END Perform work and exit==