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.3"; # 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" && vbm
"STATUS:Recipient watch directory detected:\"$recipientDir\"";
990 #### Initialize variable indicating outcome of pubkey review
991 unset updateRecipients
992 #### Add existing recipients
993 candRecPubKeysValid
=("${recPubKeysValidStatic[@]}");
994 #### Parse files in recipientDir
995 for file in "$recipientDir"/*; do
996 ##### Read first line of each file
997 recFileLine
="$(head -n1 "$file")" && vbm
"STATUS:Checking if pubkey:\"$recFileLine\"";
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") && vbm
"STATUS:RecDir pubkey is valid pubkey:\"$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[@]}") && vbm
"STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[@]}\"";
1014 yell
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
1017 # Handle case if '-R' set but '-e' not set
1018 if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then
1019 yell
"ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi;
1020 } # Update recPubKeysValid with argRecDir
1021 magicParseRecipientArgs
() {
1022 # Desc: Parses recipient arguments specified by '-r' option
1023 # Input: vars: OPTION_ENCRYPT from processArguments()
1024 # arry: argRecPubKeys from processArguments()
1025 # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX
1026 # arry: recPubKeysValid, recPubKeysValidStatic
1027 # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments()
1030 # Check if encryption option active.
1031 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then
1032 if checkapp age
; then # Check that age is available.
1033 for pubkey
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
1034 vbm
"DEBUG:Testing pubkey string:$pubkey";
1035 if checkAgePubkey
"$pubkey" && \
1036 ( validateInput
"$pubkey" "ssh_pubkey" || validateInput
"$pubkey" "age_pubkey"); then
1037 #### Form age recipient string
1038 recipients
="$recipients""-r '$pubkey' ";
1039 vbm
"STATUS:Added pubkey for forming age recipient string:""$pubkey";
1040 vbm
"DEBUG:recipients:""$recipients";
1041 #### Add validated pubkey to recPubKeysValid array
1042 recPubKeysValid
+=("$pubkey") && vbm
"DEBUG:recPubkeysValid:pubkey added:$pubkey";
1044 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
1047 vbm
"DEBUG:Finished processing argRecPubKeys array";
1048 vbm
"STATUS:Array of validated pubkeys:${recPubKeysValid[@]}";
1049 recPubKeysValidStatic
="${recPubKeysValid[@]}"; # Save static image of pubkeys validated by this function
1051 ## Form age command string
1052 CMD_ENCRYPT
="age ""$recipients " && vbm
"CMD_ENCRYPT:$CMD_ENCRYPT";
1053 CMD_ENCRYPT_SUFFIX
=".age" && vbm
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX";
1055 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
1058 CMD_ENCRYPT
="tee /dev/null " && vbm
"CMD_ENCRYPT:$CMD_ENCRYPT";
1059 CMD_ENCRYPT_SUFFIX
="" && vbm
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX";
1060 vbm
"DEBUG:Encryption not enabled."
1062 # Catch case if '-e' is set but '-r' or '-R' is not
1063 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECIPIENTS" = "true" ]]; then
1064 yell
"ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
1065 # Catch case if '-r' or '-R' set but '-e' is not
1066 if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then
1067 yell
"ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
1068 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
1069 magicParseCompressionArg
() {
1070 # Desc: Parses compression arguments specified by '-c' option
1071 # Input: vars: OPTION_COMPRESS
1072 # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX
1073 # Depends: checkapp(), vbm(), gzip,
1074 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
1075 if checkapp
gzip; then # Check if gzip available
1076 CMD_COMPRESS
="gzip " && vbm
"CMD_COMPRESS:$CMD_COMPRESS";
1077 CMD_COMPRESS_SUFFIX
=".gz" && vbm
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX";
1079 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
1082 CMD_COMPRESS
="tee /dev/null " && vbm
"CMD_COMPRESS:$CMD_COMPRESS";
1083 CMD_COMPRESS_SUFFIX
="" && vbm
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX";
1084 vbm
"DEBUG:Compression not enabled.";
1086 } # Form compression cmd string and filename suffix
1087 magicInitWorkingDir
() {
1088 # Desc: Determine temporary working directory from defaults or user input
1089 # Usage: magicInitWorkignDir
1090 # Input: vars: OPTION_TEMPDIR, argTempDirPriority, DIR_TMP_DEFAULT
1091 # Input: vars: SCRIPT_TIME_START
1092 # Output: vars: DIR_TMP
1093 # Depends: processArguments(), vbm(), yell()
1094 # Parse '-t' option (user-specified temporary working dir)
1095 ## Set DIR_TMP_PARENT to user-specified value if specified
1096 local DIR_TMP_PARENT
1098 if [[ "$OPTION_TMPDIR" = "true" ]]; then
1099 if [[ -d "$argTempDirPriority" ]]; then
1100 DIR_TMP_PARENT
="$argTempDirPriority";
1102 yell
"WARNING:Specified temporary working directory not valid:$argTempDirPriority";
1103 exit 1; # Exit since user requires a specific temp dir and it is not available.
1106 ## Set DIR_TMP_PARENT to default or fallback otherwise
1107 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
1108 DIR_TMP_PARENT
="$DIR_TMP_DEFAULT";
1109 elif [[ -d /tmp
]]; then
1110 yell
"WARNING:$DIR_TMP_DEFAULT not available. Falling back to /tmp .";
1111 DIR_TMP_PARENT
="/tmp";
1113 yell
"ERROR:No valid working directory available. Exiting.";
1117 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
1118 DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().
1119 } # Sets working dir
1120 magicParseCustomTTL
() {
1121 # Desc: Set user-specified TTLs for buffer and script
1122 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
1123 # Input: vars: OPTION_CUSTOM_BUFFERTTL, OPTION_CUSTOM_SCRIPTTTL
1124 # Input: vars: BUFFER_TTL (integer), SCRIPT_TTL_TE (string)
1125 # Output: BUFFER_TTL (integer), SCRIPT_TTL_TE (string)
1126 # Depends validateInput(), showUsage(), yell
1128 # React to '-b, --buffer-ttl' option
1129 if [[ "$OPTION_CUSTOM_BUFFERTTL" = "true" ]]; then
1130 ## T: Check if argCustomBufferTTL is an integer
1131 if validateInput
"$argCustomBufferTTL" "integer"; then
1132 ### T: argCustomBufferTTL is an integer
1133 BUFFER_TTL
="$argCustomBufferTTL";
1135 ### F: argcustomBufferTTL is not an integer
1136 yell
"ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1;
1138 ## F: do not change BUFFER_TTL
1141 # React to '-B, --script-ttl' option
1142 if [[ "$OPTION_CUSTOM_SCRIPTTTL_TE" = "true" ]]; then
1143 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
1144 if validateInput
"$argCustomScriptTTL" "time_element"; then
1145 ### T: argCustomScriptTTL is a time element
1146 SCRIPT_TTL_TE
="$argCustomScriptTTL";
1148 ### F: argcustomScriptTTL is not a time element
1149 yell
"ERROR:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1;
1151 ## F: do not change SCRIPT_TTL_TE
1153 } # Sets custom script or buffer TTL if specified
1158 processArguments
"$@";
1159 ## Act upon arguments
1160 ### Determine working directory
1161 magicInitWorkingDir
; # Sets DIR_TMP from argTempDirPriority
1162 ### Set output encryption and compression option strings
1163 #### React to "-r" ("encryption recipients") option
1164 magicParseRecipientArgs
; # Updates recPubKeysValid, CMD_ENCRYPT[_SUFFIX] from argRecPubKeys
1165 #### React to "-c" ("compression") option
1166 magicParseCompressionArg
; # Updates CMD_COMPRESS[_SUFFIX]
1167 #### React to "-R" ("recipient directory") option
1168 magicParseRecipientDir
; # Updates recPubKeysValid
1169 #### React to custom buffer and script TTL options ("-b", "-B")
1170 magicParseCustomTTL
; # Sets custom SCRIPT_TTL_TE and/or BUFFER_TTL if specified
1172 # Check that critical apps and dirs are available, display missing ones.
1173 if ! checkapp gpspipe
tar && ! checkdir
"$DIR_OUT" "DIR_TMP"; then
1174 yell
"ERROR:Critical components missing.";
1175 displayMissing
; yell
"Exiting."; exit 1; fi
1177 # Set script lifespan (SCRIPT_TTL from SCRIPT_TTL_TE)
1178 magicSetScriptTTL
"$SCRIPT_TTL_TE";
1179 ## Note: SCRIPT_TTL_TE is time element string (ex: "day") while SCRIPT_TTL is integer seconds
1181 # File name substring (ISO-8601 duration from BUFFER_TTL)
1182 bufferTTL_STR
="$(timeDuration "$BUFFER_TTL")";
1184 # Init temp working dir
1185 try mkdir
"$DIR_TMP" && vbm
"DEBUG:Working dir creatd at:$DIR_TMP";
1187 # Initialize 'tar' archive
1188 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
1189 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
1190 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
1191 ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise.
1192 checkMakeTar
"$PATHOUT_TAR" && vbm
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR";
1193 ## Append VERSION file to PATHOUT_TAR
1196 # Define GPS conversion commands
1197 CMD_CONV_NMEA
="tee /dev/null " && vbm
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough
1198 CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX
1199 CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML
1201 # MAIN LOOP:Record gps data until script lifespan ends
1202 while [[ "$SECONDS" -lt "$SCRIPT_TTL" ]]; do
1203 magicParseRecipientDir
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==