8d0f16f147ad28a41e15058600043cc7a3c9e956
[EVA-2020-02.git] / exec / bkgpslog
1 #!/bin/bash
2
3 # Desc: Records gps data until midnight
4 # Author: Steven Baltakatei Sandoval; License: GPLv3+
5 # Usage: bkgpslog -o [output dir]
6
7 #==BEGIN Define script parameters==
8 ## Logging Behavior parameters
9 BUFFER_TTL="60"; # time between file writes
10 SCRIPT_TTL="day"; # (day|hour)
11 #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option
12 DIR_TMP_DEFAULT="/dev/shm"; # Default parent of working directory
13
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="bkgpslog 0.1.0"; # Define version of script.
18
19 declare -Ag appRollCall # Associative array for storing app status
20 declare -Ag fileRollCall # Associative array for storing file status
21 declare -Ag dirRollCall # Associative array for storing dir status
22 declare -a recPubKeys # for processArguments function
23 declare recipients # for main function
24
25 ## Initialize variables
26 OPTION_VERBOSE=""; OPTION_ENCRYPT=""; OPTION_COMPRESS=""; OPTION_TMPDIR="";
27
28 #===BEGIN Declare local script functions===
29 checkapp() {
30 # Desc: If arg is a command, save result in assoc array 'appRollCall'
31 # Usage: checkapp arg1 arg2 arg3 ...
32 # Input: global assoc. array 'appRollCall'
33 # Output: adds/updates key(value) to global assoc array 'appRollCall'
34 local returnState
35 #echo "DEBUG:$(date +%S.%N)..Starting checkapp function."
36 #echo "DEBUG:args: $@"
37 #echo "DEBUG:returnState:$returnState"
38
39 #===Process Args===
40 for arg in "$@"; do
41 #echo "DEBUG:processing arg:$arg"
42 if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
43 appRollCall[$arg]="true";
44 #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]}
45 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
46 else
47 appRollCall[$arg]="false"; returnState="false";
48 fi
49 done
50
51 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
52 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
53
54 #===Determine function return code===
55 if [ "$returnState" = "true" ]; then
56 #echo "DEBUG:checkapp returns true for $arg";
57 return 0;
58 else
59 #echo "DEBUG:checkapp returns false for $arg";
60 return 1;
61 fi
62 } # Check that app exists
63 checkfile() {
64 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
65 # Usage: checkfile arg1 arg2 arg3 ...
66 # Input: global assoc. array 'fileRollCall'
67 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
68 # Output: returns 0 if app found, 1 otherwise
69 local returnState
70
71 #===Process Args===
72 for arg in "$@"; do
73 #echo "DEBUG:processing arg:$arg"
74 if [ -f "$arg" ]; then
75 fileRollCall["$arg"]="true";
76 #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]}
77 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
78 else
79 fileRollCall["$arg"]="false"; returnState="false";
80 fi
81 done
82
83 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done
84 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
85
86 #===Determine function return code===
87 if [ "$returnState" = "true" ]; then
88 #echo "DEBUG:checkapp returns true for $arg";
89 return 0;
90 else
91 #echo "DEBUG:checkapp returns false for $arg";
92 return 1;
93 fi
94 } # Check that file exists
95 checkdir() {
96 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
97 # Usage: checkdir arg1 arg2 arg3 ...
98 # Input: global assoc. array 'dirRollCall'
99 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
100 # Output: returns 0 if app found, 1 otherwise
101 local returnState
102
103 #===Process Args===
104 for arg in "$@"; do
105 #echo "DEBUG:processing arg:$arg"
106 if [ -d "$arg" ]; then
107 dirRollCall["$arg"]="true";
108 #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]}
109 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
110 elif [ "$arg" = "" ]; then
111 dirRollCall["$arg"]="false"; returnState="false";
112 else
113 returnState="false";
114 fi
115 done
116
117 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done
118 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
119
120 #===Determine function return code===
121 if [ "$returnState" = "true" ]; then
122 #echo "DEBUG:checkapp returns true for $arg";
123 return 0;
124 else
125 #echo "DEBUG:checkapp returns false for $arg";
126 return 1;
127 fi
128 } # Check that dir exists
129
130 # Yell, Die, Try Three-Fingered Claw technique
131 # Ref/Attrib: https://stackoverflow.com/a/25515370
132 yell() { echo "$0: $*" >&2; }
133 die() { yell "$*"; exit 111; }
134 try() { "$@" || die "cannot $*"; }
135
136 echoerr() {
137 echo "$@" 1>&2; # Define stderr echo function.
138 } # Define stderr message function.
139 showUsage() {
140 echoerr "USAGE:"
141 echoerr " bkgpslog [ options ]"
142 echoerr
143 echoerr "OPTIONS:"
144 echoerr " -h, --help"
145 echoerr " Display help information."
146 echoerr
147 echoerr " --version"
148 echoerr " Display script version."
149 echoerr
150 echoerr " -v, --verbose"
151 echoerr " Display debugging info."
152 echoerr
153 echoerr " -e, --encrypt"
154 echoerr " Encrypt output."
155 echoerr
156 echoerr " -r, --recipient [ pubkey string ]"
157 echoerr " Specify recipient."
158 echoerr
159 echoerr " -o, --output [ directory ]"
160 echoerr " Specify output directory to save logs."
161 echoerr
162 echoerr " -c, --compress"
163 echoerr " Compress output with gzip (before encryption if enabled)."
164 echoerr
165 echoerr "EXAMPLE: (bash script lines)"
166 echoerr "/bin/bash bkgpslog -e -c \\"
167 echoerr "-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
168 echoerr "-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
169 echoerr "-o ~/Sync/Location"
170 } # Display information on how to use this script.
171 showVersion() {
172 echoerr "$SCRIPT_VERSION"
173 } # Display script version.
174 vbm() {
175 # Usage: vbm "DEBUG:verbose message here"
176 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
177 # Input:
178 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
179 # - "$@" positional arguments fed to this function.
180 # Output: stderr
181 # Script function dependencies: echoerr
182 # External function dependencies: echo
183 # Last modified: 2020-04-11T23:57Z
184 # Last modified by: Steven Baltakatei Sandoval
185 # License: GPLv3+
186 # Ref./Attrib:
187
188 if [ "$OPTION_VERBOSE" = "true" ]; then
189 FUNCTION_TIME=$(date --iso-8601=ns); # Save current time in nano seconds.
190 echoerr "[$FUNCTION_TIME] ""$*"; # Display argument text.
191 fi
192
193 # End function
194 return 0; # Function finished.
195 } # Verbose message display function.
196 processArguments() {
197 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
198 #echoerr "DEBUG:Starting processArguments while loop."
199 #echoerr "DEBUG:Provided arguments are:""$*"
200 case "$1" in
201 -h | --help) showUsage; exit 1;; # Display usage.
202 --version) showVersion; exit 1;; # Show version
203 -v | --verbose) OPTION_VERBOSE="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode.
204 -o | --output) if [ -d "$2" ]; then DIR_OUT="$2"; vbm "DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory.
205 -e | --encrypt) OPTION_ENCRYPT="true"; vbm "DEBUG:Encrypted output mode enabled.";;
206 -r | --recipient) # Add 'age' recipient via public key string
207 recPubKeys+=("$2"); vbm "pubkey added:""$2"; shift;;
208 -c | --compress) OPTION_COMPRESS="true"; vbm "DEBUG:Compressed output mode enabled.";;
209 -z | --time-zone) try setTimeZoneEV "$1";;
210 -t | --temp-dir) OPTION_TMPDIR="true" && TMP_DIR_PRIORITY="$1";;
211 *) echoerr "ERROR: Unrecognized argument."; exit 1;; # Handle unrecognized options.
212 esac
213 shift
214 done
215 } # Argument Processing
216 setTimeZoneEV(){
217 # Desc: Set time zone environment variable TZ
218 # Usage: setTimeZoneEV arg1
219 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
220 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
221 # Output: exports TZ
222 # exit code 0 on success
223 # exit code 1 on incorrect number of arguments
224 # exit code 2 if unable to validate arg1
225 # Depends: yell, printenv, bash 5
226 # Tested on: Debian 10
227 ARG1="$1"
228 local tzDir returnState
229 if ! [[ $# -eq 1 ]]; then
230 yell "ERROR:Invalid argument count.";
231 return 1;
232 fi
233
234 # Read TZDIR env var if available
235 if printenv TZDIR 1>/dev/null 2>&1; then
236 tzDir="$(printenv TZDIR)";
237 else
238 tzDir="/usr/share/zoneinfo";
239 fi
240
241 # Validate TZ string
242 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
243 yell "ERROR:Invalid time zone argument.";
244 return 2;
245 else
246 # Export ARG1 as TZ environment variable
247 TZ="$ARG1" && export TZ && returnState="true";
248 fi
249
250 # Determine function return code
251 if [ "$returnState" = "true" ]; then
252 return 0;
253 fi
254 } # Exports TZ environment variable
255 timeUntilNextDay(){
256 # Desc: Report seconds until next day.
257 # Output: stdout: integer seconds until next day
258 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
259 # Usage: timeUntilNextDay
260 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
261 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
262 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
263 TIME_NEXT_DAY="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
264 SECONDS_UNTIL_NEXT_DAY="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
265 if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then
266 returnState="true";
267 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then
268 returnState="WARNING_ZERO";
269 yell "WARNING:Reported time until next day exactly zero.";
270 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then
271 returnState="WARNING_NEGATIVE";
272 yell "WARNING:Reported time until next day is negative.";
273 fi
274
275 try echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
276
277 #===Determine function return code===
278 if [[ "$returnState" = "true" ]]; then
279 return 0;
280 elif [[ "$returnState" = "WARNING_ZERO" ]]; then
281 return 1;
282 elif [[ "$returnState" = "WARNING_NEGATIVE" ]]; then
283 return 2;
284 fi
285 } # Report seconds until next day
286 timeUntilNextHour(){
287 # Desc: Report seconds until next hour
288 # Output: stdout: integer seconds until next hour
289 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
290 # Usage: timeUntilNextHour
291 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
292 local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
293 TIME_CURRENT="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
294 TIME_NEXT_HOUR="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
295 SECONDS_UNTIL_NEXT_HOUR="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second).
296 if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then
297 returnState="true";
298 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then
299 returnState="WARNING_ZERO";
300 yell "WARNING:Reported time until next hour exactly zero.";
301 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then
302 returnState="WARNING_NEGATIVE";
303 yell "WARNING:Reported time until next hour is negative.";
304 fi
305
306 try echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
307
308 #===Determine function return code===
309 if [[ "$returnState" = "true" ]]; then
310 return 0;
311 elif [[ "$returnState" = "WARNING_ZERO" ]]; then
312 return 1;
313 elif [[ "$returnState" = "WARNING_NEGATIVE" ]]; then
314 return 2;
315 fi
316 } # Report seconds until next hour
317 dateTimeShort(){
318 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
319 # Usage: dateTimeShort
320 # Output: stdout: timestamp (ISO-8601, no separators)
321 local TIME_CURRENT TIME_CURRENT_SHORT
322 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
323 TIME_CURRENT_SHORT="$(date -d "$TIME_CURRENT" +%Y%m%dT%H%M%S%z)"; # Produce separator-less current timestamp with resolution 1 second.
324 echo "$TIME_CURRENT_SHORT";
325 } # Get YYYYmmddTHHMMSS±zzzz
326 dateShort(){
327 # Desc: Date without separators (YYYYmmdd)
328 # Usage: dateShort
329 # Output: stdout: date (ISO-8601, no separators)
330 local TIME_CURRENT DATE_CURRENT_SHORT
331 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
332 DATE_CURRENT_SHORT="$(date -d "$TIME_CURRENT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
333 echo "$DATE_CURRENT_SHORT";
334 } # Get YYYYmmdd
335 timeDuration(){
336 # Desc: Output approximate time duration string before given time (default:current date)
337 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
338 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
339 # Usage: timeDuration [arg1] ([arg2])
340 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
341 # arg2: precision level (optional; default=2)
342 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
343 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
344 # Depends: date 8 (gnucoreutils)
345 local returnState fullHours fullMinutes fullSeconds;
346 ARG1="$1";
347 ARG2="$2";
348 precision=2; # set default precision
349 returnState="true"; # set default return state
350
351 # Check that between one and two arguments is supplied
352 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
353 yell "ERROR:Invalid number of arguments:$# . Exiting.";
354 returnState="ERROR_INPUT"; fi
355
356 # Check that arg1 provided
357 if [[ $# -ge 1 ]]; then
358 # Check that arg1 is a positive integer
359 if [[ "$ARG1" =~ ^[[:digit:]]+$ ]]; then
360 arg1Valid="true";
361 else
362 yell "ERROR:ARG1 not a digit.";
363 returnState="ERROR_INPUT";
364 arg1Valid="false";
365 fi
366 else
367 yell "ERROR:No argument provided. Exiting.";
368 exit 1;
369 fi
370
371 # Consider whether arg2 was provided
372 if [[ $# -eq 2 ]]; then
373 # Check that the second arg is a positive integer
374 if [[ "$ARG2" =~ ^[[:digit:]]+$ ]] && [[ "ARG2" -gt 0 ]]; then
375 arg2Valid="true";
376 precision="$ARG2";
377 else
378 yell "ERROR:ARG2 not a positive integer. (is $ARG2 ). Leaving early.";
379 returnState="ERROR_INPUT";
380 arg2Valid="false";
381 fi;
382 else
383 arg2Valid="false";
384 fi;
385
386 remainder="$ARG1" ; # seconds
387 ## Calculate full years Y, update remainder
388 fullYears=$(( remainder / (365*24*60*60) ));
389 remainder=$(( remainder - (fullYears*365*24*60*60) ));
390 ## Calculate full months M, update remainder
391 fullMonths=$(( remainder / (30*24*60*60) ));
392 remainder=$(( remainder - (fullMonths*30*24*60*60) ));
393 ## Calculate full days D, update remainder
394 fullDays=$(( remainder / (24*60*60) ));
395 remainder=$(( remainder - (fullDays*24*60*60) ));
396 ## Calculate full hours H, update remainder
397 fullHours=$(( remainder / (60*60) ));
398 remainder=$(( remainder - (fullHours*60*60) ));
399 ## Calculate full minutes M, update remainder
400 fullMinutes=$(( remainder / (60) ));
401 remainder=$(( remainder - (fullMinutes*60) ));
402 ## Calculate full seconds S, update remainder
403 fullSeconds=$(( remainder / (1) ));
404 remainder=$(( remainder - (remainder*1) ));
405 ## Check which fields filled
406 if [[ $fullYears -gt 0 ]]; then hasYears="true"; else hasYears="false"; fi
407 if [[ $fullMonths -gt 0 ]]; then hasMonths="true"; else hasMonths="false"; fi
408 if [[ $fullDays -gt 0 ]]; then hasDays="true"; else hasDays="false"; fi
409 if [[ $fullHours -gt 0 ]]; then hasHours="true"; else hasHours="false"; fi
410 if [[ $fullMinutes -gt 0 ]]; then hasMinutes="true"; else hasMinutes="false"; fi
411 if [[ $fullSeconds -gt 0 ]]; then hasSeconds="true"; else hasSeconds="false"; fi
412
413 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
414 witherPrecision="false"
415
416 ### Years
417 if $hasYears && [[ $precision -gt 0 ]]; then
418 displayYears="true";
419 witherPrecision="true";
420 else
421 displayYears="false";
422 fi;
423 if $witherPrecision; then ((precision--)); fi;
424
425 ### Months
426 if $hasMonths && [[ $precision -gt 0 ]]; then
427 displayMonths="true";
428 witherPrecision="true";
429 else
430 displayMonths="false";
431 fi;
432 if $witherPrecision && [[ $precision -gt 0 ]]; then
433 displayMonths="true";
434 fi;
435 if $witherPrecision; then ((precision--)); fi;
436
437 ### Days
438 if $hasDays && [[ $precision -gt 0 ]]; then
439 displayDays="true";
440 witherPrecision="true";
441 else
442 displayDays="false";
443 fi;
444 if $witherPrecision && [[ $precision -gt 0 ]]; then
445 displayDays="true";
446 fi;
447 if $witherPrecision; then ((precision--)); fi;
448
449 ### Hours
450 if $hasHours && [[ $precision -gt 0 ]]; then
451 displayHours="true";
452 witherPrecision="true";
453 else
454 displayHours="false";
455 fi;
456 if $witherPrecision && [[ $precision -gt 0 ]]; then
457 displayHours="true";
458 fi;
459 if $witherPrecision; then ((precision--)); fi;
460
461 ### Minutes
462 if $hasMinutes && [[ $precision -gt 0 ]]; then
463 displayMinutes="true";
464 witherPrecision="true";
465 else
466 displayMinutes="false";
467 fi;
468 if $witherPrecision && [[ $precision -gt 0 ]]; then
469 displayMinutes="true";
470 fi;
471 if $witherPrecision; then ((precision--)); fi;
472
473 ### Seconds
474
475 if $hasSeconds && [[ $precision -gt 0 ]]; then
476 displaySeconds="true";
477 witherPrecision="true";
478 else
479 displaySeconds="false";
480 fi;
481 if $witherPrecision && [[ $precision -gt 0 ]]; then
482 displaySeconds="true";
483 fi;
484 if $witherPrecision; then ((precision--)); fi;
485
486
487
488 ## Determine whether or not the "T" separator is needed to separate date and time elements
489 if ( $displayHours || $displayMinutes || $displaySeconds); then
490 displayDateTime="true"; else displayDateTime="false"; fi
491
492 ## Construct duration output string
493 OUTPUT="P"
494 if $displayYears; then
495 OUTPUT=$OUTPUT$fullYears"Y"; fi
496 if $displayMonths; then
497 OUTPUT=$OUTPUT$fullMonths"M"; fi
498 if $displayDays; then
499 OUTPUT=$OUTPUT$fullDays"D"; fi
500 if $displayDateTime; then
501 OUTPUT=$OUTPUT"T"; fi
502 if $displayHours; then
503 OUTPUT=$OUTPUT$fullHours"H"; fi
504 if $displayMinutes; then
505 OUTPUT=$OUTPUT$fullMinutes"M"; fi
506 if $displaySeconds; then
507 OUTPUT=$OUTPUT$fullSeconds"S"; fi
508
509 ## Output duration string to stdout
510 if [[ "$returnState" = "true" ]]; then echo "$OUTPUT"; fi
511
512 #===Determine function return code===
513 if [ "$returnState" = "true" ]; then
514 return 0;
515 else
516 echo "$returnState" 1>&2;
517 return 1;
518 fi
519
520 } # Get duration (ex: PT10M4S )
521 displayMissing() {
522 # Desc: Displays missing apps, files, and dirs
523 # Usage: displayMissing
524 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
525 # Output: stderr messages
526 #==BEGIN Display errors==
527 #===BEGIN Display Missing Apps===
528 missingApps="Missing apps :"
529 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
530 for key in "${!appRollCall[@]}"; do
531 value="${appRollCall[$key]}"
532 if [ "$value" = "false" ]; then
533 #echo "DEBUG:Missing apps: $key => $value";
534 missingApps="$missingApps""$key "
535 appMissing="true"
536 fi
537 done
538 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
539 echo "$missingApps" 1>&2;
540 fi
541 #===END Display Missing Apps===
542
543 #===BEGIN Display Missing Files===
544 missingFiles="Missing files:"
545 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
546 for key in "${!fileRollCall[@]}"; do
547 value="${fileRollCall[$key]}"
548 if [ "$value" = "false" ]; then
549 #echo "DEBUG:Missing files: $key => $value";
550 missingFiles="$missingFiles""$key "
551 fileMissing="true"
552 fi
553 done
554 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
555 echo "$missingFiles" 1>&2;
556 fi
557 #===END Display Missing Files===
558
559 #===BEGIN Display Missing Directories===
560 missingDirs="Missing dirs:"
561 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
562 for key in "${!dirRollCall[@]}"; do
563 value="${dirRollCall[$key]}"
564 if [ "$value" = "false" ]; then
565 #echo "DEBUG:Missing dirs: $key => $value";
566 missingDirs="$missingDirs""$key "
567 dirMissing="true"
568 fi
569 done
570 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
571 echo "$missingDirs" 1>&2;
572 fi
573 #===END Display Missing Directories===
574
575 #==END Display errors==
576 } # Display missing apps, files, dirs
577 setScriptTTL() {
578 #Desc: Sets script TTL
579 #Usage: setScriptTTL arg1
580 #Input: arg1: "day" or "hour"
581 #Output: scriptTTL
582 #Depends: timeUntilNextHour or timeUntilNextDay
583 local ARG1
584 ARG1="$1"
585 if [[ "$ARG1" = "day" ]]; then
586 # Set script lifespan to end at start of next day
587 if ! scriptTTL="$(timeUntilNextDay)"; then
588 if [[ "$scriptTTL" -eq 0 ]]; then
589 ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
590 else
591 yell "ERROR: timeUntilNextDay exit code $?"; exit 1;
592 fi;
593 fi;
594 elif [[ "$ARG1" = "hour" ]]; then
595 # Set script lifespan to end at start of next hour
596 if ! scriptTTL="$(timeUntilNextHour)"; then
597 if [[ "$scriptTTL" -eq 0 ]]; then
598 ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
599 else
600 yell "ERROR: timeUntilNextHour exit code $?"; exit 1;
601 fi;
602 fi;
603 else
604 yell "ERROR:Invalid argument for setScriptTTL function."; exit 1;
605 fi
606 } # Seconds until next (day|hour).
607 main() {
608 processArguments "$@" # Process arguments.
609
610 # Determine working directory
611 ## Set DIR_TMP_PARENT to user-specified value if specified
612 if [[ "$OPTION_TMPDIR" = "true" ]]; then
613 if [[ -d "$TMP_DIR_PRIORITY" ]]; then
614 DIR_TMP_PARENT="$OPTION_TMPDIR";
615 else
616 yell "WARNING:Specified temporary working directory not valid:$OPTION_TMP_DIR";
617 exit 1;
618 fi
619 fi
620
621 ## Set DIR_TMP_PARENT to default or fallback otherwise
622 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
623 DIR_TMP_PARENT="$DIR_TMP_DEFAULT";
624 elif [[ -d /tmp ]]; then
625 yell "WARNING:/dev/shm not available. Falling back to /tmp .";
626 DIR_TMP_PARENT="/tmp";
627 else
628 yell "ERROR:No valid working directory available. Exiting.";
629 exit 1;
630 fi
631
632 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
633 DIR_TMP="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog"; # Note: removed at end of main().
634
635 # Set output encryption and compression option strings
636 if [[ "$OPTION_ENCRYPT" = "true" ]]; then # Check if encryption option active.
637 if checkapp age; then # Check that age is available.
638 for pubkey in "${recPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
639 vbm "DEBUG:Testing pubkey string:$pubkey"
640 if echo "butts" | age -a -r "$pubkey" 1>/dev/null; then
641 # Form age recipient string
642 recipients="$recipients""-r $pubkey ";
643 vbm "Added pubkey for forming age recipient string:""$pubkey";
644 vbm "DEBUG:recipients:""$recipients";
645 else
646 yell "ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
647 fi
648 done
649 vbm "DEBUG:Finished processing recPubKeys array";
650 # Form age command string
651 CMD_ENCRYPT="age ""$recipients ";
652 CMD_ENCRYPT_SUFFIX=".age";
653 else
654 yell "ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
655 fi
656 else
657 CMD_ENCRYPT="tee /dev/null ";
658 CMD_ENCRYPT_SUFFIX="";
659 vbm "DEBUG:Encryption not enabled."
660 fi
661 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
662 if checkapp gzip; then # Check if gzip available
663 CMD_COMPRESS="gzip ";
664 CMD_COMPRESS_SUFFIX=".gz";
665 else
666 yell "ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
667 fi
668 else
669 CMD_COMPRESS="tee /dev/null ";
670 CMD_COMPRESS_SUFFIX="";
671 vbm "DEBUG:Compression not enabled."
672 fi
673
674 # Check that critical apps and dirs are available, displag missing ones.
675 if ! checkapp gpspipe tar && ! checkdir "$DIR_OUT" "/dev/shm"; then
676 yell "ERROR:Critical components missing.";
677 displayMissing; yell "Exiting."; exit 1; fi
678
679 # Set script lifespan
680 setScriptTTL "$SCRIPT_TTL";
681
682 # File name substring: encoded bufferTTL
683 bufferTTL_STR="$(timeDuration $BUFFER_TTL)";
684
685 # Init temp working dir
686 try mkdir "$DIR_TMP" && vbm "DEBUG:Working dir creatd at:$DIR_TMP"
687
688 # Initialize 'tar' archive
689 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
690 PATHOUT_TAR="$DIR_OUT"/"$(dateShort)".."$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar
691 ## Write bkgpslog version to DIR_TMP/VERSION
692 echo "$0"" Version:""$SCRIPT_VERSION" >> "$DIR_TMP/VERSION" && vbm "DEBUG:VERSION created."
693 ## Create empty tar archive at PATHOUT_TAR
694 try tar --create --directory="$DIR_TMP" --file="$PATHOUT_TAR" --files-from=/dev/null && vbm "DEBUG:""$PATHOUT_TAR"" created."
695 ## Append VERSION file to PATHOUT_TAR
696 try tar --append --directory="$DIR_TMP" --file="$PATHOUT_TAR" "VERSION" && vbm "DEBUG:VERSION added to $PATHOUT_TAR"
697
698 # Record gps data until script lifespan ends
699 declare debugCounter; debugCounter="0"; # set debug counter
700 timeStart=$(dateTimeShort); # Note start time
701 while [[ "$SECONDS" -lt "$scriptTTL" ]]; do
702 # Determine file paths (time is start of buffer period)
703 FILEOUT_BASENAME="$timeStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" ; # ISO-8601 YYYYmmddTHHMMSS+zzP[$bufferTTL]S
704 ## Files saved to DIR_TMP
705 PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" ;
706 PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" ;
707 PATHOUT_KML="$DIR_TMP"/"$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" ;
708 ## Files saved to disk (DIR_OUT)
709 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
710 PATHOUT_TAR="$DIR_OUT"/"$(dateShort)".."$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar
711 # Define GPS conversion commands
712 CMD_CONV_NMEA="tee /dev/null " ; # tee as passthrough
713 CMD_CONV_GPX="gpsbabel -i nmea -f - -o gpx -F - " ; # convert NMEA to GPX
714 CMD_CONV_KML="gpsbabel -i nmea -f - -o kml -F - " ; # convert NMEA to KML
715 # Fill Bash variable buffer
716 bufferBash="$(timeout "$BUFFER_TTL""s" gpspipe -r)"; # Record gpspipe nmea data to buffer for bufferTTL seconds
717 # Process bufferBash, save secured chunk set to DIR_TMP
718 echo "$bufferBash" | $CMD_CONV_NMEA | $CMD_COMPRESS | $CMD_ENCRYPT > "$PATHOUT_NMEA" & # Create NMEA file (secured if requested)
719 echo "$bufferBash" | $CMD_CONV_GPX | $CMD_COMPRESS | $CMD_ENCRYPT > "$PATHOUT_GPX" & # Create GPX file (secured if requested)
720 echo "$bufferBash" | $CMD_CONV_KML | $CMD_COMPRESS | $CMD_ENCRYPT > "$PATHOUT_KML" & # Create KML file (secured if requested)
721 vbm "DEBUG:Completed buffer session $debugCounter ." 1>&2;
722 # Append each secured chunk in memory dir (DIR_TMP) to file on disk (PATHOUT_TAR in DIR_OUT)
723 try tar --append --directory="$(dirname $"PATHOUT_NMEA")" --file="$PATHOUT_TAR" "$(basename "$PATHOUT_NMEA")" && \
724 vbm "DEBUG:Appended NMEA location data $PATHOUT_NMEA to $PATHOUT_TAR";
725 try tar --append --directory="$(dirname $"PATHOUT_GPX")" --file="$PATHOUT_TAR" "$(basename "$PATHOUT_GPX")" && \
726 vbm "DEBUG:Appended GPX location data $PATHOUT_GPX to $PATHOUT_TAR";
727 try tar --append --directory="$(dirname $"PATHOUT_KML")" --file="$PATHOUT_TAR" "$(basename "$PATHOUT_KML")" && \
728 vbm "DEBUG:Appended KML location $PATHOUT_KML to $PATHOUT_TAR";
729 # Remove secured chunks from DIR_TMP
730 try rm "$PATHOUT_NMEA" "$PATHOUT_NMEA" "$PATHOUT_NMEA";
731 # Reset buffer and filenames
732 unset bufferBash FILEOUT_BASENAME PATHOUT_NMEA PATHOUT_GPX PATHOUT_KML PATHOUT_TAR;
733 ((debugCounter++))
734 done
735
736 # Remove DIR_TMP
737 try rm -r "$DIR_TMP";
738 } # Main function.
739 #===END Declare local script functions===
740 #==END Define script parameters==
741
742
743 #==BEGIN Perform work and exit==
744 main "$@" # Run main function.
745 exit 0;
746 #==END Perform work and exit==