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_TE
="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.4.0";          # 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 [ string pubkey ]" 
 156     echoerr 
"            Specify recipient. May be age or ssh pubkey." 
 157     echoerr 
"            May be specified multiple times for multiple pubkeys." 
 158     echoerr 
"            See https://github.com/FiloSottile/age" 
 159     echoerr 
"    -o, --output [ path dir ]" 
 160     echoerr 
"            Specify output directory to save logs." 
 161     echoerr 
"    -c, --compress" 
 162     echoerr 
"            Compress output with gzip (before encryption if enabled)." 
 163     echoerr 
"    -z, --time-zone" 
 164     echoerr 
"            Specify time zone. (ex: \"America/New_York\")" 
 165     echoerr 
"    -t, --temp-dir [path dir]" 
 166     echoerr 
"            Specify parent directory for temporary working directory." 
 167     echoerr 
"            Default: \"/dev/shm\"" 
 168     echoerr 
"    -R, --recipient-dir [path dir]" 
 169     echoerr 
"            Specify directory containing files whose first lines are" 
 170     echoerr 
"            to be interpreted as pubkey strings (see \\'-r\\' option)." 
 171     echoerr 
"    -b, --buffer-ttl [integer]" 
 172     echoerr 
"            Specify custom buffer period in seconds (default: 300 seconds)" 
 173     echoerr 
"    -B, --script-ttl [integer]" 
 174     echoerr 
"            Specify custom script time-to-live in seconds (default: \"day\")" 
 176     echoerr 
"EXAMPLE: (bash script lines)" 
 177     echoerr 
"/bin/bash bkgpslog -v -e -c \\" 
 178     echoerr 
"-z \"UTC\" -t \"/dev/shm\" \\" 
 179     echoerr 
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\" 
 180     echoerr 
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\" 
 181     echoerr 
"-o ~/Sync/Location" 
 182 } # Display information on how to use this script. 
 184     echoerr 
"$SCRIPT_VERSION" 
 185 } # Display script version. 
 187     # Usage: vbm "DEBUG:verbose message here" 
 188     # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true". 
 190     #   - OPTION_VERBOSE  variable set by processArguments function. (ex: "true", "false") 
 191     #   - "$@"            positional arguments fed to this function. 
 193     # Script function dependencies: echoerr 
 194     # External function dependencies: echo 
 195     # Last modified: 2020-04-11T23:57Z 
 196     # Last modified by: Steven Baltakatei Sandoval 
 200     if [ "$OPTION_VERBOSE" = "true" ]; then 
 201         FUNCTION_TIME
=$(date --iso-8601=ns); # Save current time in nano seconds. 
 202         echoerr 
"[$FUNCTION_TIME] ""$*"; # Display argument text. 
 206     return 0; # Function finished. 
 207 } # Verbose message display function. 
 209     while [ ! $# -eq 0 ]; do   # While number of arguments ($#) is not (!) equal to (-eq) zero (0). 
 210         #echoerr "DEBUG:Starting processArguments while loop." 
 211         #echoerr "DEBUG:Provided arguments are:""$*" 
 213             -h | --help) showUsage
; exit 1;; # Display usage. 
 214             --version) showVersion
; exit 1;; # Show version 
 215             -v | --verbose) OPTION_VERBOSE
="true"; vbm 
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. 
 216             -o | --output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm 
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory. 
 217             -e | --encrypt) OPTION_ENCRYPT
="true"; vbm 
"DEBUG:Encrypted output mode enabled.";; # Enable encryption 
 218             -r | --recipient) OPTION_RECIPIENTS
="true"; argRecPubKeys
+=("$2"); vbm 
"STATUS:pubkey added:""$2"; shift;; # Add recipients 
 219             -c | --compress) OPTION_COMPRESS
="true"; vbm 
"DEBUG:Compressed output mode enabled.";; # Enable compression 
 220             -z | --time-zone) try setTimeZoneEV 
"$2"; shift;; # Set timestamp timezone 
 221             -t | --temp-dir) OPTION_TMPDIR
="true" && argTempDirPriority
="$2"; shift;; # Set time zone 
 222             -R | --recipient-dir) OPTION_RECIPIENTS
="true"; OPTION_RECDIR
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir 
 223             -b | --buffer-ttl) OPTION_CUSTOM_BUFFERTTL
="true" && argCustomBufferTTL
="$2"; shift;; # Set custom buffer period (default: 300 seconds) 
 224             -B | --script-ttl) OPTION_CUSTOM_SCRIPTTTL_TE
="true" && argCustomScriptTTL
="$2"; shift;; # Set custom script TTL (default: "day") 
 225             *) echoerr 
"ERROR: Unrecognized argument: $1"; echoerr 
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. 
 229 } # Argument Processing 
 231     # Desc: Set time zone environment variable TZ 
 232     # Usage: setTimeZoneEV arg1 
 233     # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") 
 234     #        TZDIR env var (optional; default: "/usr/share/zoneinfo") 
 236     #         exit code 0 on success 
 237     #         exit code 1 on incorrect number of arguments 
 238     #         exit code 2 if unable to validate arg1 
 239     # Depends: yell, printenv, bash 5 
 240     # Tested on: Debian 10 
 242     local tzDir returnState
 
 243     if ! [[ $# -eq 1 ]]; then 
 244         yell 
"ERROR:Invalid argument count."; 
 248     # Read TZDIR env var if available 
 249     if printenv TZDIR 
1>/dev
/null 
2>&1; then 
 250         tzDir
="$(printenv TZDIR)"; 
 252         tzDir
="/usr/share/zoneinfo"; 
 256     if ! [[ -f "$tzDir"/"$ARG1" ]]; then 
 257         yell 
"ERROR:Invalid time zone argument."; 
 260     # Export ARG1 as TZ environment variable 
 261         TZ
="$ARG1" && export TZ 
&& returnState
="true"; 
 264     # Determine function return code 
 265     if [ "$returnState" = "true" ]; then 
 268 } # Exports TZ environment variable 
 270     # Desc: Report seconds until next day. 
 272     # Output: stdout: integer seconds until next day 
 273     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 274     # Usage: timeUntilNextDay 
 275     # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi 
 276     # Depends: date 8, echo 8, yell, try 
 278     local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
 
 280     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 281     TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. 
 282     SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second). 
 283     if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then 
 285     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then 
 286         returnState
="warning_zero"; 
 287         yell 
"WARNING:Reported time until next day exactly zero."; 
 288     elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then 
 289         returnState
="warning_negative"; 
 290         yell 
"WARNING:Reported time until next day is negative."; 
 293     try 
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report 
 295     # Determine function return code 
 296     if [[ "$returnState" = "true" ]]; then 
 298     elif [[ "$returnState" = "warning_zero" ]]; then 
 300     elif [[ "$returnState" = "warning_negative" ]]; then 
 303 } # Report seconds until next day 
 305     # Desc: Report seconds until next hour 
 307     # Output: stdout: integer seconds until next hour 
 308     # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 
 309     # Usage: timeUntilNextHour 
 310     # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi 
 312     local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
 
 313     TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 314     TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. 
 315     SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second). 
 316     if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then 
 318     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then 
 319         returnState
="warning_zero"; 
 320         yell 
"WARNING:Reported time until next hour exactly zero."; 
 321     elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then 
 322         returnState
="warning_negative"; 
 323         yell 
"WARNING:Reported time until next hour is negative."; 
 326     try 
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report 
 328     # Determine function return code 
 329     if [[ "$returnState" = "true" ]]; then 
 331     elif [[ "$returnState" = "warning_zero" ]]; then 
 333     elif [[ "$returnState" = "warning_negative" ]]; then 
 336 } # Report seconds until next hour 
 338     # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) 
 339     # Usage: dateTimeShort ([str date]) 
 341     # Input: arg1: 'date'-parsable timestamp string (optional) 
 342     # Output: stdout: timestamp (ISO-8601, no separators) 
 344     local TIME_CURRENT TIME_CURRENT_SHORT
 
 348     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 349     # Decide to parse current or supplied date 
 350     ## Check if time argument empty 
 351     if [[ -z "$argTime" ]]; then 
 352         ## T: Time argument empty, use current time 
 353         TIME_INPUT
="$TIME_CURRENT"; 
 355         ## F: Time argument exists, validate time 
 356         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 357             ### T: Time argument is valid; use it 
 358             TIME_INPUT
="$argTime"; 
 360             ### F: Time argument not valid; exit 
 361             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 364     # Construct and deliver separator-les date string 
 365     TIME_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)"; 
 366     echo "$TIME_CURRENT_SHORT"; 
 367 } # Get YYYYmmddTHHMMSS±zzzz 
 369     # Desc: Date without separators (YYYYmmdd) 
 370     # Usage: dateShort ([str date]) 
 372     # Input: arg1: 'date'-parsable timestamp string (optional) 
 373     # Output: stdout: date (ISO-8601, no separators) 
 375     local TIME_CURRENT DATE_CURRENT_SHORT
 
 379     TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. 
 380     # Decide to parse current or supplied date 
 381     ## Check if time argument empty 
 382     if [[ -z "$argTime" ]]; then 
 383         ## T: Time argument empty, use current time 
 384         TIME_INPUT
="$TIME_CURRENT"; 
 386         ## F: Time argument exists, validate time 
 387         if date --date="$argTime" 1>/dev
/null 
2>&1; then 
 388             ### T: Time argument is valid; use it 
 389             TIME_INPUT
="$argTime"; 
 391             ### F: Time argument not valid; exit 
 392             yell 
"ERROR:Invalid time argument supplied. Exiting."; exit 1; 
 395     # Construct and deliver separator-les date string     
 396     DATE_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. 
 397     echo "$DATE_CURRENT_SHORT"; 
 400     # Desc: Given seconds, output ISO-8601 duration string 
 401     # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information 
 402     # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) 
 403     # Usage: timeDuration [1:seconds] ([2:precision]) 
 405     # Input: arg1: seconds as base 10 integer >= 0  (ex: 3601) 
 406     #        arg2: precision level (optional; default=2) 
 407     # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") 
 408     #         exit code 0: success 
 409     #         exit code 1: error_input 
 410     #         exit code 2: error_unknown 
 411     # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' 
 412     # Depends: date 8 (gnucoreutils), yell,  
 413     local returnState argSeconds argPrecision remainder precision witherPrecision
 
 414     local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
 
 415     local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
 
 416     local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
 
 418     argSeconds
="$1"; # read arg1 (seconds) 
 419     argPrecision
="$2"; # read arg2 (precision) 
 420     precision
=2; # set default precision 
 422     # Check that between one and two arguments is supplied 
 423     if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then 
 424         yell 
"ERROR:Invalid number of arguments:$# . Exiting."; 
 425         returnState
="error_input"; fi 
 427     # Check that argSeconds provided 
 428     if [[ $# -ge 1 ]]; then 
 429         ## Check that argSeconds is a positive integer 
 430         if [[ "$argSeconds" =~ ^
[[:digit
:]]+$ 
]]; then 
 433             yell 
"ERROR:argSeconds not a digit."; 
 434             returnState
="error_input"; 
 437         yell 
"ERROR:No argument provided. Exiting."; 
 441     # Consider whether argPrecision was provided 
 442     if  [[ $# -eq 2 ]]; then 
 443         # Check that argPrecision is a positive integer 
 444         if [[ "$argPrecision" =~ ^
[[:digit
:]]+$ 
]] && [[ "$argPrecision" -gt 0 ]]; then 
 445         precision
="$argPrecision"; 
 447             yell 
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; 
 448             returnState
="error_input"; 
 454     remainder
="$argSeconds" ; # seconds 
 455     ## Calculate full years Y, update remainder 
 456     fullYears
=$(( remainder / (365*24*60*60) )); 
 457     remainder
=$(( remainder - (fullYears*365*24*60*60) )); 
 458     ## Calculate full months M, update remainder 
 459     fullMonths
=$(( remainder / (30*24*60*60) )); 
 460     remainder
=$(( remainder - (fullMonths*30*24*60*60) )); 
 461     ## Calculate full days D, update remainder 
 462     fullDays
=$(( remainder / (24*60*60) )); 
 463     remainder
=$(( remainder - (fullDays*24*60*60) )); 
 464     ## Calculate full hours H, update remainder 
 465     fullHours
=$(( remainder / (60*60) )); 
 466     remainder
=$(( remainder - (fullHours*60*60) )); 
 467     ## Calculate full minutes M, update remainder 
 468     fullMinutes
=$(( remainder / (60) )); 
 469     remainder
=$(( remainder - (fullMinutes*60) )); 
 470     ## Calculate full seconds S, update remainder 
 471     fullSeconds
=$(( remainder / (1) )); 
 472     remainder
=$(( remainder - (remainder*1) )); 
 473     ## Check which fields filled 
 474     if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi 
 475     if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi 
 476     if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi 
 477     if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi 
 478     if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi 
 479     if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi 
 481     ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) 
 482     witherPrecision
="false" 
 485     if $hasYears && [[ $precision -gt 0 ]]; then 
 487         witherPrecision
="true"; 
 489         displayYears
="false"; 
 491     if $witherPrecision; then ((precision
--)); fi; 
 494     if $hasMonths && [[ $precision -gt 0 ]]; then 
 495         displayMonths
="true"; 
 496         witherPrecision
="true"; 
 498         displayMonths
="false"; 
 500     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 501         displayMonths
="true"; 
 503     if $witherPrecision; then ((precision
--)); fi; 
 506     if $hasDays && [[ $precision -gt 0 ]]; then 
 508         witherPrecision
="true"; 
 512     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 515     if $witherPrecision; then ((precision
--)); fi; 
 518     if $hasHours && [[ $precision -gt 0 ]]; then 
 520         witherPrecision
="true"; 
 522         displayHours
="false"; 
 524     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 527     if $witherPrecision; then ((precision
--)); fi; 
 530     if $hasMinutes && [[ $precision -gt 0 ]]; then 
 531         displayMinutes
="true"; 
 532         witherPrecision
="true"; 
 534         displayMinutes
="false"; 
 536     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 537         displayMinutes
="true"; 
 539     if $witherPrecision; then ((precision
--)); fi; 
 543     if $hasSeconds && [[ $precision -gt 0 ]]; then 
 544         displaySeconds
="true"; 
 545         witherPrecision
="true"; 
 547         displaySeconds
="false"; 
 549     if $witherPrecision && [[ $precision -gt 0 ]]; then 
 550         displaySeconds
="true"; 
 552     if $witherPrecision; then ((precision
--)); fi; 
 554     ## Determine whether or not the "T" separator is needed to separate date and time elements 
 555     if ( $displayHours || $displayMinutes || $displaySeconds); then 
 556         displayDateTime
="true"; else displayDateTime
="false"; fi 
 558     ## Construct duration output string 
 560     if $displayYears; then 
 561         OUTPUT
=$OUTPUT$fullYears"Y"; fi 
 562     if $displayMonths; then 
 563         OUTPUT
=$OUTPUT$fullMonths"M"; fi 
 564     if $displayDays; then 
 565         OUTPUT
=$OUTPUT$fullDays"D"; fi 
 566     if $displayDateTime; then 
 567         OUTPUT
=$OUTPUT"T"; fi 
 568     if $displayHours; then 
 569         OUTPUT
=$OUTPUT$fullHours"H"; fi 
 570     if $displayMinutes; then 
 571         OUTPUT
=$OUTPUT$fullMinutes"M"; fi 
 572     if $displaySeconds; then 
 573         OUTPUT
=$OUTPUT$fullSeconds"S"; fi 
 575     ## Output duration string to stdout 
 576     echo "$OUTPUT" && returnState
="true"; 
 578     #===Determine function return code=== 
 579     if [ "$returnState" = "true" ]; then 
 581     elif [ "$returnState" = "error_input" ]; then 
 585         yell 
"ERROR:Unknown"; 
 589 } # Get duration (ex: PT10M4S ) 
 591     # Desc: Displays missing apps, files, and dirs 
 592     # Usage: displayMissing 
 593     # Input: associative arrays: appRollCall, fileRollCall, dirRollCall 
 594     # Output: stderr messages 
 595     #==BEGIN Display errors== 
 596     #===BEGIN Display Missing Apps=== 
 597     missingApps
="Missing apps  :" 
 598     #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done 
 599     for key 
in "${!appRollCall[@]}"; do 
 600         value
="${appRollCall[$key]}" 
 601         if [ "$value" = "false" ]; then 
 602             #echo "DEBUG:Missing apps: $key => $value"; 
 603             missingApps
="$missingApps""$key " 
 607     if [ "$appMissing" = "true" ]; then  # Only indicate if an app is missing. 
 608         echo "$missingApps" 1>&2; 
 610     #===END Display Missing Apps=== 
 612     #===BEGIN Display Missing Files=== 
 613     missingFiles
="Missing files:" 
 614     #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done 
 615     for key 
in "${!fileRollCall[@]}"; do 
 616         value
="${fileRollCall[$key]}" 
 617         if [ "$value" = "false" ]; then 
 618             #echo "DEBUG:Missing files: $key => $value"; 
 619             missingFiles
="$missingFiles""$key " 
 623     if [ "$fileMissing" = "true" ]; then  # Only indicate if an app is missing. 
 624         echo "$missingFiles" 1>&2; 
 626     #===END Display Missing Files=== 
 628     #===BEGIN Display Missing Directories=== 
 629     missingDirs
="Missing dirs:" 
 630     #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done 
 631     for key 
in "${!dirRollCall[@]}"; do 
 632         value
="${dirRollCall[$key]}" 
 633         if [ "$value" = "false" ]; then 
 634             #echo "DEBUG:Missing dirs: $key => $value"; 
 635             missingDirs
="$missingDirs""$key " 
 639     if [ "$dirMissing" = "true" ]; then  # Only indicate if an dir is missing. 
 640         echo "$missingDirs" 1>&2; 
 642     #===END Display Missing Directories=== 
 644     #==END Display errors== 
 645 } # Display missing apps, files, dirs 
 646 magicSetScriptTTL
() { 
 647     #Desc: Sets script_TTL seconds from provided time_element string argument 
 648     #Usage: magicSetScriptTTL [str time_element] 
 649     #Input: arg1: string (Ex: SCRIPT_TTL_TE; "day" or "hour") 
 650     #Output: var: SCRIPT_TTL (integer seconds) 
 651     #Depends: timeUntilNextHour, timeUntilNextDay 
 654     if [[ "$argTimeElement" = "day" ]]; then 
 655             # Set script lifespan to end at start of next day 
 656         if ! SCRIPT_TTL
="$(timeUntilNextDay)"; then 
 657             if [[ "$SCRIPT_TTL" -eq 0 ]]; then 
 658             ((SCRIPT_TTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 660             yell 
"ERROR: timeUntilNextDay exit code $?"; exit 1; 
 663     elif [[ "$argTimeElement" = "hour" ]]; then 
 664         # Set script lifespan to end at start of next hour 
 665         if ! SCRIPT_TTL
="$(timeUntilNextHour)"; then 
 666             if [[ "$SCRIPT_TTL" -eq 0 ]]; then 
 667                 ((SCRIPT_TTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout. 
 669                 yell 
"ERROR: timeUntilNextHour exit code $?"; exit 1; 
 673         yell 
"ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1; 
 675 } # Seconds until next (day|hour). 
 677     # Desc: Checks that a valid tar archive exists, creates one otherwise 
 678     # Usage: checkMakeTar [ path ] 
 680     # Input: arg1: path of tar archive 
 681     # Output: exit code 0 : tar readable 
 682     #         exit code 1 : tar missing; created 
 683     #         exit code 2 : tar not readable; moved; replaced 
 684     # Depends: try, tar, date 
 685     local PATH_TAR returnFlag0 returnFlag1 returnFlag2
 
 688     # Check if file is a valid tar archive 
 689     if tar --list --file="$PATH_TAR" 1>/dev
/null 
2>&1; then 
 690         ## T1: return success 
 691         returnFlag0
="tar valid"; 
 693         ## F1: Check if file exists 
 694         if [[ -f "$PATH_TAR" ]]; then 
 696             try 
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
 
 697                 returnFlag1
="tar moved"; 
 702         ## F2: Create tar archive, return 0 
 703         try 
tar --create --file="$PATH_TAR" --files-from=/dev
/null 
&& \
 
 704             returnFlag2
="tar created"; 
 707     # Determine function return code 
 708     if [[ "$returnFlag0" = "tar valid" ]]; then 
 710     elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then 
 711         return 1; # tar missing so created 
 712     elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then 
 713         return 2; # tar not readable so moved; replaced 
 715 } # checks if arg1 is tar; creates one otherwise 
 717     # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar 
 718     # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 720     # Input: arg1: data to be written 
 721     #        arg2: file name of file to be inserted into tar 
 722     #        arg3: tar archive path (must exist first) 
 723     #        arg4: temporary working dir 
 724     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 725     # Output: file written to disk 
 726     # Example: decrypt multiple large files in parallel 
 727     #          appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 728     #          appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 729     #          appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 731     # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 
 734     local FN
="${FUNCNAME[0]}"; 
 735     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 738     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 740     # Check tar path is a file 
 741     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 744     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 746     # Set command strings 
 747     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 748     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 749     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 750     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 756     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 757     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 758     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 759     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 760     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 761     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 762     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 763     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 765     # Write to temporary working dir 
 766     eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME"; 
 769     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 770     #yell "DEBUG:STATUS:$FN:Finished appendArgTar()." 
 771 } # Append Bash var to file appended to Tar archive 
 773     # Desc: Processes first file and then appends to tar 
 774     # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) 
 776     # Input: arg1: path of file to be (processed and) written 
 777     #        arg2: name to use for file inserted into tar 
 778     #        arg3: tar archive path (must exist first) 
 779     #        arg4: temporary working dir 
 780     #        arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") 
 781     # Output: file written to disk 
 782     # Example: decrypt multiple large files in parallel 
 783     #          appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & 
 784     #          appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & 
 785     #          appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & 
 789     local FN
="${FUNCNAME[0]}"; 
 790     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 793     if ! [ -z "$2" ]; then FILENAME
="$2"; else yell 
"ERROR:$FN:Not enough arguments."; exit 1; fi 
 794     # Check tar path is a file 
 795     if [ -f "$3" ]; then TAR_PATH
="$3"; else yell 
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi 
 797     if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell 
"ERROR:$FN:No temporary working dir set."; exit 1; fi 
 798     # Set command strings 
 799     if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1 
 800     if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2 
 801     if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3 
 802     if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4 
 804     # Input command string 
 808     # yell "DEBUG:STATUS:$FN:CMD0:$CMD0" 
 809     # yell "DEBUG:STATUS:$FN:CMD1:$CMD1" 
 810     # yell "DEBUG:STATUS:$FN:CMD2:$CMD2" 
 811     # yell "DEBUG:STATUS:$FN:CMD3:$CMD3" 
 812     # yell "DEBUG:STATUS:$FN:CMD4:$CMD4" 
 813     # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME" 
 814     # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH" 
 815     # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR" 
 817     # Write to temporary working dir 
 818     eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME"; 
 821     try 
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME"; 
 822     #yell "DEBUG:STATUS:$FN:Finished appendFileTar()." 
 823 } # Append file to Tar archive 
 825     # Desc: Checks if string is an age-compatible pubkey 
 826     # Usage: checkAgePubkey [str pubkey] 
 828     # Input: arg1: string 
 829     # Output: return code 0: string is age-compatible pubkey 
 830     #         return code 1: string is NOT an age-compatible pubkey 
 831     #         age stderr (ex: there is stderr if invalid string provided) 
 832     # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 ) 
 836     if echo "test" | age 
-a -r "$argPubkey" 1>/dev
/null
; then 
 843     # Desc: Validates Input 
 844     # Usage: validateInput [str input] [str input type] 
 846     # Input: arg1: string to validate 
 847     #        arg2: string specifying input type (ex:"ssh_pubkey") 
 848     # Output: return code 0: if input string matched specified string type 
 849     # Depends: bash 5, yell 
 852     local FN
="${FUNCNAME[0]}"; 
 857     if [[ $# -gt 2 ]]; then yell 
"ERROR:$0:$FN:Too many arguments."; exit 1; fi; 
 860     if [[ -z "$argInput" ]]; then return 1; fi 
 864     ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") 
 865     if [[ "$argType" = "ssh_pubkey" ]]; then 
 866         if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\ ]*[[:alnum
:]+/=]*$ 
]]; then 
 870     ### Check for age1[:bech32:] 
 871     if [[ "$argType" = "age_pubkey" ]]; then 
 872         if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$ 
]]; then 
 876     if [[ "$argType" = "integer" ]]; then 
 877         if [[ "$argInput" =~ ^
[[:digit
:]]*$ 
]]; then 
 880     ## time element (year, month, week, day, hour, minute, second) 
 881     if [[ "$argType" = "time_element" ]]; then 
 882         if [[ "$argInput" = "year" ]] || \
 
 883                [[ "$argInput" = "month" ]] || \
 
 884                [[ "$argInput" = "week" ]] || \
 
 885                [[ "$argInput" = "day" ]] || \
 
 886                [[ "$argInput" = "hour" ]] || \
 
 887                [[ "$argInput" = "minute" ]] || \
 
 888                [[ "$argInput" = "second" ]]; then 
 891     # Return error if no condition matched. 
 893 } # Validates strings 
 894 magicWriteVersion
() { 
 895     # Desc: Appends time-stamped VERSION to PATHOUT_TAR 
 896     # Usage: magicWriteVersion 
 898     # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP 
 899     # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME 
 900     # Output: appends tar PATHOUT_TAR 
 901     # Depends: dateTimeShort, appendArgTar 
 902     local CONTENT_VERSION pubKeyIndex
 
 904     # Set VERSION file name 
 905     FILEOUT_VERSION
="$(dateTimeShort)..VERSION"; 
 907     # Gather VERSION data in CONTENT_VERSION 
 908     CONTENT_VERSION
="SCRIPT_VERSION=$SCRIPT_VERSION"; 
 909     #CONTENT_VERSION="$CONTENT_VERSION""\\n"; 
 910     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME"; 
 911     CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL"; 
 912     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION"; 
 913     CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL"; 
 914     CONTENT_VERSION
="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)"; 
 915     CONTENT_VERSION
="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME"; 
 916     ## Add list of recipient pubkeys 
 917     for pubkey 
in "${recPubKeysValid[@]}"; do 
 919         CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey"; 
 921     ## Process newline escapes 
 922     CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")" 
 924     # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR 
 925     appendArgTar 
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP"; 
 927 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar() 
 928 magicGatherWriteBuffer
() { 
 929     # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR 
 930     # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP, 
 931     # Inputs: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX 
 932     # Depends: yell, try, vbm, appendArgTar, tar 
 933     local FN
="${FUNCNAME[0]}"; 
 934     wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) 
 935     # Create buffer file with unique name 
 936     PATHOUT_BUFFER
="$DIR_TMP/buffer$SECONDS"; 
 938     timeout 
"$BUFFER_TTL"s gpspipe 
-r -o "$PATHOUT_BUFFER" ; 
 939     timeBufferStart
="$(dateTimeShort  "$(date --date="$BUFFER_TTL seconds ago")")"; # Note start time 
 940     vbm "DEBUG
:STATUS
:$FN:Started magicWriteBuffer
().
"; 
 941     # Determine file paths (time is start of buffer period) 
 942     FILEOUT_BASENAME="$timeBufferStart""--""$bufferTTL_STR""..
""$SCRIPT_HOSTNAME""_location
" && vbm "STATUS
:Set FILEOUT_BASENAME to
:$FILEOUT_BASENAME"; 
 943     ## Files saved to DIR_TMP 
 944     FILEOUT_NMEA="$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_NMEA to
:$FILEOUT_NMEA"; 
 945     FILEOUT_GPX="$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_GPX to
:$FILEOUT_GPX"; 
 946     FILEOUT_KML="$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS
:Set FILEOUT_KML to
:$FILEOUT_KML"; 
 947     PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_NMEA" && vbm "STATUS
:Set PATHOUT_NMEA to
:$PATHOUT_NMEA"; 
 948     PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_GPX" && vbm "STATUS
:Set PATHOUT_GPX to
:$PATHOUT_GPX"; 
 949     PATHOUT_KML="$DIR_TMP"/"$FILEOUT_KML" && vbm "STATUS
:Set PATHOUT_KML to
:$PATHOUT_KML"; 
 950     ## Files saved to disk (DIR_OUT) 
 951     ### one file per day (Ex: "20200731..hostname_location.
[.gpx.gz
].
tar") 
 952     PATHOUT_TAR="$DIR_OUT"/"$(dateShort "$(date --date="$BUFFER_TTL seconds ago")")"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
 953         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
 955     vbm 
"STATUS:DIR_TMP     :$DIR_TMP"; 
 956     vbm 
"STATUS:PATHOUT_TAR :$PATHOUT_TAR"; 
 957     vbm 
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA"; 
 958     vbm 
"STATUS:PATHOUT_GPX:$PATHOUT_GPX"; 
 959     vbm 
"STATUS:PATHOUT_KML:$PATHOUT_KML"; 
 962     # Validate PATHOUT_TAR as tar. 
 963     checkMakeTar 
"$PATHOUT_TAR"; 
 964     ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) 
 965     if [[ $? 
-eq 1 ]] || [[ $? 
-eq 2 ]]; then magicWriteVersion
; fi 
 967     # Write bufferBash to PATHOUT_TAR 
 968     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data 
 969     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file 
 970     appendFileTar 
"$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file 
 972     # Remove secured chunks from DIR_TMP 
 973     rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML"; 
 974     vbm 
"DEBUG:STATUS:$FN:Finished magicWriteBuffer()."; 
 975 } # write buffer to disk 
 976 magicParseRecipientDir
() { 
 977     # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory") 
 978     # Inputs:  vars: OPTION_RECDIR, argRecDir, OPTION_ENCRYPT 
 979     #          arry: recPubKeysValid 
 980     # Outputs: arry: recPubKeysValid 
 981     # Depends: processArguments, 
 982     local recFileLine updateRecipients recipientDir
 
 983     declare -a candRecPubKeysValid
 
 985     # Check that '-e' and '-R' set 
 986     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then 
 987         ### Check that argRecDir is a directory. 
 988         if [[ -d "$argRecDir" ]]; then 
 989             recipientDir
="$argRecDir"; 
 990             #### Initialize variable indicating outcome of pubkey review 
 991             unset updateRecipients
 
 992             #### Add existing recipients 
 993             candRecPubKeysValid
=("${recPubKeysValid[@]}"); 
 994             #### Parse files in recipientDir 
 995             for file in "$recipientDir"/*; do 
 996                 ##### Read first line of each file 
 997                 recFileLine
="$(head -n1 "$file")"; 
 998                 ##### check if first line is a valid pubkey 
 999                 if checkAgePubkey 
"$recFileLine" && \
 
1000                         ( validateInput 
"$recFileLine" "ssh_pubkey" || validateInput 
"$recFileLine" "age_pubkey"); then 
1001                     ###### T: add candidate pubkey to candRecPubKeysValid 
1002                     candRecPubKeysValid
+=("$recFileLine"); 
1004                     ###### F: throw warning; 
1005                     yell 
"ERROR:Invalid recipient file detected. Not modifying recipient list." 
1006                     updateRecipients
="false"; 
1009             #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected 
1010             if ! [[ "$updateRecipients" = "false" ]]; then 
1011                 recPubKeysValid
=("${candRecPubKeysValid[@]}"); 
1014             yell 
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1; 
1017     # Handle case if '-e' set but '-R' not set 
1018     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECDIR" = "true" ]]; then 
1019         yell 
"ERROR: \\'-e\\' set but \\'-R\\' is not set."; fi; 
1020     # Handle case if '-R' set but '-e' not set 
1021     if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then 
1022         yell 
"ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi; 
1023 } # Update recPubKeysValid with argRecDir 
1024 magicParseRecipientArgs
() { 
1025     # Desc: Parses recipient arguments specified by '-r' option 
1026     # Input:  vars: OPTION_ENCRYPT from processArguments() 
1027     #         arry: argRecPubKeys from processArguments() 
1028     # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX 
1029     #         arry: recPubKeysValid 
1030     # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments() 
1033     # Check if encryption option active. 
1034     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then  
1035         if checkapp age
; then # Check that age is available. 
1036             for pubkey 
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message 
1037                 vbm 
"DEBUG:Testing pubkey string:$pubkey"; 
1038                 if checkAgePubkey 
"$pubkey" && \
 
1039                         ( validateInput 
"$pubkey" "ssh_pubkey" || validateInput 
"$pubkey" "age_pubkey"); then 
1040                     #### Form age recipient string 
1041                     recipients
="$recipients""-r '$pubkey' "; 
1042                     vbm 
"STATUS:Added pubkey for forming age recipient string:""$pubkey"; 
1043                     vbm 
"DEBUG:recipients:""$recipients"; 
1044                     #### Add validated pubkey to recPubKeysValid array 
1045                     recPubKeysValid
+=("$pubkey") && vbm 
"DEBUG:recPubkeysValid:pubkey added:$pubkey"; 
1047                     yell 
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1; 
1050             vbm 
"DEBUG:Finished processing argRecPubKeys array"; 
1052             ##  Form age command string 
1053             CMD_ENCRYPT
="age ""$recipients " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
1054             CMD_ENCRYPT_SUFFIX
=".age" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
1056             yell 
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1; 
1059         CMD_ENCRYPT
="tee /dev/null " && vbm 
"CMD_ENCRYPT:$CMD_ENCRYPT"; 
1060         CMD_ENCRYPT_SUFFIX
="" && vbm 
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX"; 
1061         vbm 
"DEBUG:Encryption not enabled." 
1063     # Catch case if '-e' is set but '-r' or '-R' is not 
1064     if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECIPIENTS" = "true" ]]; then 
1065         yell 
"ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi; 
1066     # Catch case if '-r' or '-R' set but '-e' is not 
1067     if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then 
1068         yell 
"ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;  
1069 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix 
1070 magicParseCompressionArg
() { 
1071     # Desc: Parses compression arguments specified by '-c' option 
1072     # Input:  vars: OPTION_COMPRESS 
1073     # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX 
1074     # Depends: checkapp(), vbm(), gzip,  
1075     if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active 
1076         if checkapp 
gzip; then # Check if gzip available 
1077             CMD_COMPRESS
="gzip " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1078             CMD_COMPRESS_SUFFIX
=".gz" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1080             yell 
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1; 
1083         CMD_COMPRESS
="tee /dev/null " && vbm 
"CMD_COMPRESS:$CMD_COMPRESS"; 
1084         CMD_COMPRESS_SUFFIX
="" && vbm 
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX"; 
1085         vbm 
"DEBUG:Compression not enabled."; 
1087 } # Form compression cmd string and filename suffix 
1088 magicInitWorkingDir
() { 
1089     # Desc: Determine temporary working directory from defaults or user input 
1090     # Usage: magicInitWorkignDir 
1091     # Input:  vars: OPTION_TEMPDIR, argTempDirPriority, DIR_TMP_DEFAULT 
1092     # Input:  vars: SCRIPT_TIME_START 
1093     # Output: vars: DIR_TMP 
1094     # Depends: processArguments(), vbm(), yell() 
1095     # Parse '-t' option (user-specified temporary working dir) 
1096     ## Set DIR_TMP_PARENT to user-specified value if specified 
1097     local DIR_TMP_PARENT
 
1099     if [[ "$OPTION_TMPDIR" = "true" ]]; then 
1100         if [[ -d "$argTempDirPriority" ]]; then 
1101             DIR_TMP_PARENT
="$argTempDirPriority";  
1103             yell 
"WARNING:Specified temporary working directory not valid:$argTempDirPriority"; 
1104             exit 1; # Exit since user requires a specific temp dir and it is not available. 
1107     ## Set DIR_TMP_PARENT to default or fallback otherwise 
1108         if [[ -d "$DIR_TMP_DEFAULT" ]]; then 
1109             DIR_TMP_PARENT
="$DIR_TMP_DEFAULT"; 
1110         elif [[ -d /tmp 
]]; then 
1111             yell 
"WARNING:$DIR_TMP_DEFAULT not available. Falling back to /tmp ."; 
1112             DIR_TMP_PARENT
="/tmp"; 
1114             yell 
"ERROR:No valid working directory available. Exiting."; 
1118     ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START) 
1119     DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm 
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().     
1120 } # Sets working dir 
1121 magicParseCustomTTL
() { 
1122     # Desc: Set user-specified TTLs for buffer and script 
1123     # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string) 
1124     # Input: vars: OPTION_CUSTOM_BUFFERTTL, OPTION_CUSTOM_SCRIPTTTL 
1125     # Input: vars: BUFFER_TTL (integer), SCRIPT_TTL_TE (string) 
1126     # Output: BUFFER_TTL (integer), SCRIPT_TTL_TE (string) 
1127     # Depends validateInput(), showUsage(), yell 
1129     # React to '-b, --buffer-ttl' option 
1130     if [[ "$OPTION_CUSTOM_BUFFERTTL" = "true" ]]; then 
1131         ## T: Check if argCustomBufferTTL is an integer 
1132         if validateInput 
"$argCustomBufferTTL" "integer"; then 
1133             ### T: argCustomBufferTTL is an integer 
1134             BUFFER_TTL
="$argCustomBufferTTL"; 
1136             ### F: argcustomBufferTTL is not an integer 
1137             yell 
"ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1; 
1139         ## F: do not change BUFFER_TTL 
1142     # React to '-B, --script-ttl' option 
1143     if [[ "$OPTION_CUSTOM_SCRIPTTTL_TE" = "true" ]]; then 
1144         ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour") 
1145         if validateInput 
"$argCustomScriptTTL" "time_element"; then 
1146             ### T: argCustomScriptTTL is a time element 
1147             SCRIPT_TTL_TE
="$argCustomScriptTTL"; 
1149             ### F: argcustomScriptTTL is not a time element 
1150             yell 
"ERROR:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1; 
1152         ## F: do not change SCRIPT_TTL_TE 
1154 } # Sets custom script or buffer TTL if specified 
1159     processArguments 
"$@"; 
1160     ## Act upon arguments 
1161     ### Determine working directory 
1162     magicInitWorkingDir
; # Sets DIR_TMP from argTempDirPriority 
1163     ### Set output encryption and compression option strings 
1164     #### React to "-r" ("encryption recipients") option 
1165     magicParseRecipientArgs
; # Updates recPubKeysValid, CMD_ENCRYPT[_SUFFIX] 
1166     #### React to "-c" ("compression") option 
1167     magicParseCompressionArg
; # Updates CMD_COMPRESS[_SUFFIX] 
1168     #### React to "-R" ("recipient directory") option 
1169     magicParseRecipientDir
; # Updates recPubKeysValid 
1170     #### React to custom buffer and script TTL options ("-b", "-B") 
1171     magicParseCustomTTL
; # Sets custom SCRIPT_TTL_TE and/or BUFFER_TTL if specified 
1173     # Check that critical apps and dirs are available, display missing ones. 
1174     if ! checkapp gpspipe 
tar && ! checkdir 
"$DIR_OUT" "DIR_TMP"; then 
1175         yell 
"ERROR:Critical components missing."; 
1176         displayMissing
; yell 
"Exiting."; exit 1; fi 
1178     # Set script lifespan (SCRIPT_TTL from SCRIPT_TTL_TE) 
1179     magicSetScriptTTL 
"$SCRIPT_TTL_TE"; 
1180     ## Note: SCRIPT_TTL_TE is time element string (ex: "day") while SCRIPT_TTL is integer seconds 
1182     # File name substring (ISO-8601 duration from BUFFER_TTL) 
1183     bufferTTL_STR
="$(timeDuration "$BUFFER_TTL")"; 
1185     # Init temp working dir 
1186     try 
mkdir "$DIR_TMP" && vbm 
"DEBUG:Working dir creatd at:$DIR_TMP"; 
1188     # Initialize 'tar' archive 
1189     ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar")) 
1190     PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
 
1191         vbm 
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR"; 
1192     ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise. 
1193     checkMakeTar 
"$PATHOUT_TAR" && vbm 
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR"; 
1194     ## Append VERSION file to PATHOUT_TAR 
1197     # Define GPS conversion commands 
1198     CMD_CONV_NMEA
="tee /dev/null " && vbm 
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough 
1199     CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm 
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX 
1200     CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm 
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML 
1202     # MAIN LOOP:Record gps data until script lifespan ends 
1203     while [[ "$SECONDS" -lt "$SCRIPT_TTL" ]]; do 
1204         magicGatherWriteBuffer 
& 
1205         sleep "$BUFFER_TTL"; 
1210     try 
rm -r "$DIR_TMP"; 
1212     vbm 
"STATUS:Main function finished."; 
1214 #===END Declare local script functions=== 
1215 #==END Define script parameters== 
1218 #==BEGIN Perform work and exit== 
1219 main 
"$@" # Run main function. 
1221 #==END Perform work and exit==