19f95f270ede42591555bf3d9f6c79d824129735
[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.2.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. May be age or ssh pubkey."
158 echoerr " See https://github.com/FiloSottile/age"
159 echoerr
160 echoerr " -o, --output [ directory ]"
161 echoerr " Specify output directory to save logs."
162 echoerr
163 echoerr " -c, --compress"
164 echoerr " Compress output with gzip (before encryption if enabled)."
165 echoerr
166 echoerr " -z, --time-zone"
167 echoerr " Specify time zone. (ex: \"America/New_York\")"
168 echoerr
169 echoerr " -t, --temp-dir"
170 echoerr " Specify parent directory for temporary working directory."
171 echoerr " Default: \"/dev/shm\""
172 echoerr
173 echoerr "EXAMPLE: (bash script lines)"
174 echoerr "/bin/bash bkgpslog -e -c \\"
175 echoerr "-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
176 echoerr "-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
177 echoerr "-o ~/Sync/Location"
178 } # Display information on how to use this script.
179 showVersion() {
180 echoerr "$SCRIPT_VERSION"
181 } # Display script version.
182 vbm() {
183 # Usage: vbm "DEBUG:verbose message here"
184 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
185 # Input:
186 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
187 # - "$@" positional arguments fed to this function.
188 # Output: stderr
189 # Script function dependencies: echoerr
190 # External function dependencies: echo
191 # Last modified: 2020-04-11T23:57Z
192 # Last modified by: Steven Baltakatei Sandoval
193 # License: GPLv3+
194 # Ref./Attrib:
195
196 if [ "$OPTION_VERBOSE" = "true" ]; then
197 FUNCTION_TIME=$(date --iso-8601=ns); # Save current time in nano seconds.
198 echoerr "[$FUNCTION_TIME] ""$*"; # Display argument text.
199 fi
200
201 # End function
202 return 0; # Function finished.
203 } # Verbose message display function.
204 processArguments() {
205 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
206 #echoerr "DEBUG:Starting processArguments while loop."
207 #echoerr "DEBUG:Provided arguments are:""$*"
208 case "$1" in
209 -h | --help) showUsage; exit 1;; # Display usage.
210 --version) showVersion; exit 1;; # Show version
211 -v | --verbose) OPTION_VERBOSE="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode.
212 -o | --output) if [ -d "$2" ]; then DIR_OUT="$2"; vbm "DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory.
213 -e | --encrypt) OPTION_ENCRYPT="true"; vbm "DEBUG:Encrypted output mode enabled.";;
214 -r | --recipient) # Add 'age' recipient via public key string
215 recPubKeys+=("$2"); vbm "STATUS:pubkey added:""$2"; shift;;
216 -c | --compress) OPTION_COMPRESS="true"; vbm "DEBUG:Compressed output mode enabled.";;
217 -z | --time-zone) try setTimeZoneEV "$2"; shift;;
218 -t | --temp-dir) OPTION_TMPDIR="true" && TMP_DIR_PRIORITY="$2"; shift;;
219 *) echoerr "ERROR: Unrecognized argument: $1"; echoerr "STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
220 esac
221 shift
222 done
223 } # Argument Processing
224 setTimeZoneEV(){
225 # Desc: Set time zone environment variable TZ
226 # Usage: setTimeZoneEV arg1
227 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
228 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
229 # Output: exports TZ
230 # exit code 0 on success
231 # exit code 1 on incorrect number of arguments
232 # exit code 2 if unable to validate arg1
233 # Depends: yell, printenv, bash 5
234 # Tested on: Debian 10
235 ARG1="$1"
236 local tzDir returnState
237 if ! [[ $# -eq 1 ]]; then
238 yell "ERROR:Invalid argument count.";
239 return 1;
240 fi
241
242 # Read TZDIR env var if available
243 if printenv TZDIR 1>/dev/null 2>&1; then
244 tzDir="$(printenv TZDIR)";
245 else
246 tzDir="/usr/share/zoneinfo";
247 fi
248
249 # Validate TZ string
250 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
251 yell "ERROR:Invalid time zone argument.";
252 return 2;
253 else
254 # Export ARG1 as TZ environment variable
255 TZ="$ARG1" && export TZ && returnState="true";
256 fi
257
258 # Determine function return code
259 if [ "$returnState" = "true" ]; then
260 return 0;
261 fi
262 } # Exports TZ environment variable
263 timeUntilNextDay(){
264 # Desc: Report seconds until next day.
265 # Version: 1.0.0
266 # Output: stdout: integer seconds until next day
267 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
268 # Usage: timeUntilNextDay
269 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
270 # Depends: date 8, echo 8, yell, try
271
272 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
273
274 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
275 TIME_NEXT_DAY="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
276 SECONDS_UNTIL_NEXT_DAY="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
277 if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then
278 returnState="true";
279 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then
280 returnState="warning_zero";
281 yell "WARNING:Reported time until next day exactly zero.";
282 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then
283 returnState="warning_negative";
284 yell "WARNING:Reported time until next day is negative.";
285 fi
286
287 try echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
288
289 # Determine function return code
290 if [[ "$returnState" = "true" ]]; then
291 return 0;
292 elif [[ "$returnState" = "warning_zero" ]]; then
293 return 1;
294 elif [[ "$returnState" = "warning_negative" ]]; then
295 return 2;
296 fi
297 } # Report seconds until next day
298 timeUntilNextHour(){
299 # Desc: Report seconds until next hour
300 # Version 1.0.0
301 # Output: stdout: integer seconds until next hour
302 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
303 # Usage: timeUntilNextHour
304 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
305
306 local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
307 TIME_CURRENT="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
308 TIME_NEXT_HOUR="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
309 SECONDS_UNTIL_NEXT_HOUR="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second).
310 if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then
311 returnState="true";
312 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then
313 returnState="warning_zero";
314 yell "WARNING:Reported time until next hour exactly zero.";
315 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then
316 returnState="warning_negative";
317 yell "WARNING:Reported time until next hour is negative.";
318 fi
319
320 try echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
321
322 # Determine function return code
323 if [[ "$returnState" = "true" ]]; then
324 return 0;
325 elif [[ "$returnState" = "warning_zero" ]]; then
326 return 1;
327 elif [[ "$returnState" = "warning_negative" ]]; then
328 return 2;
329 fi
330 } # Report seconds until next hour
331 dateTimeShort(){
332 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
333 # Usage: dateTimeShort
334 # Output: stdout: timestamp (ISO-8601, no separators)
335 local TIME_CURRENT TIME_CURRENT_SHORT
336 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
337 TIME_CURRENT_SHORT="$(date -d "$TIME_CURRENT" +%Y%m%dT%H%M%S%z)"; # Produce separator-less current timestamp with resolution 1 second.
338 echo "$TIME_CURRENT_SHORT";
339 } # Get YYYYmmddTHHMMSS±zzzz
340 dateShort(){
341 # Desc: Date without separators (YYYYmmdd)
342 # Usage: dateShort
343 # Output: stdout: date (ISO-8601, no separators)
344 local TIME_CURRENT DATE_CURRENT_SHORT
345 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
346 DATE_CURRENT_SHORT="$(date -d "$TIME_CURRENT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
347 echo "$DATE_CURRENT_SHORT";
348 } # Get YYYYmmdd
349 timeDuration(){
350 # Desc: Output approximate time duration string before given time (default:current date)
351 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
352 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
353 # Usage: timeDuration [arg1] ([arg2])
354 # Version: 1.0.1
355 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
356 # arg2: precision level (optional; default=2)
357 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
358 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
359 # Depends: date 8 (gnucoreutils), yell,
360 local returnState ARG1 ARG2 remainder precision witherPrecision
361 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
362 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
363 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
364
365 ARG1="$1";
366 ARG2="$2";
367 precision=2; # set default precision
368 returnState="true"; # set default return state
369
370 # Check that between one and two arguments is supplied
371 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
372 yell "ERROR:Invalid number of arguments:$# . Exiting.";
373 returnState="ERROR_INPUT"; fi
374
375 # Check that arg1 provided
376 if [[ $# -ge 1 ]]; then
377 ## Check that arg1 is a positive integer
378 if [[ "$ARG1" =~ ^[[:digit:]]+$ ]]; then
379 :
380 else
381 yell "ERROR:ARG1 not a digit.";
382 returnState="ERROR_INPUT";
383 fi
384 else
385 yell "ERROR:No argument provided. Exiting.";
386 exit 1;
387 fi
388
389 # Consider whether arg2 was provided
390 if [[ $# -eq 2 ]]; then
391 # Check that the second arg is a positive integer
392 if [[ "$ARG2" =~ ^[[:digit:]]+$ ]] && [[ "$ARG2" -gt 0 ]]; then
393 precision="$ARG2";
394 else
395 yell "ERROR:ARG2 not a positive integer. (is $ARG2 ). Leaving early.";
396 returnState="ERROR_INPUT";
397 fi;
398 else
399 :
400 fi;
401
402 remainder="$ARG1" ; # seconds
403 ## Calculate full years Y, update remainder
404 fullYears=$(( remainder / (365*24*60*60) ));
405 remainder=$(( remainder - (fullYears*365*24*60*60) ));
406 ## Calculate full months M, update remainder
407 fullMonths=$(( remainder / (30*24*60*60) ));
408 remainder=$(( remainder - (fullMonths*30*24*60*60) ));
409 ## Calculate full days D, update remainder
410 fullDays=$(( remainder / (24*60*60) ));
411 remainder=$(( remainder - (fullDays*24*60*60) ));
412 ## Calculate full hours H, update remainder
413 fullHours=$(( remainder / (60*60) ));
414 remainder=$(( remainder - (fullHours*60*60) ));
415 ## Calculate full minutes M, update remainder
416 fullMinutes=$(( remainder / (60) ));
417 remainder=$(( remainder - (fullMinutes*60) ));
418 ## Calculate full seconds S, update remainder
419 fullSeconds=$(( remainder / (1) ));
420 remainder=$(( remainder - (remainder*1) ));
421 ## Check which fields filled
422 if [[ $fullYears -gt 0 ]]; then hasYears="true"; else hasYears="false"; fi
423 if [[ $fullMonths -gt 0 ]]; then hasMonths="true"; else hasMonths="false"; fi
424 if [[ $fullDays -gt 0 ]]; then hasDays="true"; else hasDays="false"; fi
425 if [[ $fullHours -gt 0 ]]; then hasHours="true"; else hasHours="false"; fi
426 if [[ $fullMinutes -gt 0 ]]; then hasMinutes="true"; else hasMinutes="false"; fi
427 if [[ $fullSeconds -gt 0 ]]; then hasSeconds="true"; else hasSeconds="false"; fi
428
429 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
430 witherPrecision="false"
431
432 ### Years
433 if $hasYears && [[ $precision -gt 0 ]]; then
434 displayYears="true";
435 witherPrecision="true";
436 else
437 displayYears="false";
438 fi;
439 if $witherPrecision; then ((precision--)); fi;
440
441 ### Months
442 if $hasMonths && [[ $precision -gt 0 ]]; then
443 displayMonths="true";
444 witherPrecision="true";
445 else
446 displayMonths="false";
447 fi;
448 if $witherPrecision && [[ $precision -gt 0 ]]; then
449 displayMonths="true";
450 fi;
451 if $witherPrecision; then ((precision--)); fi;
452
453 ### Days
454 if $hasDays && [[ $precision -gt 0 ]]; then
455 displayDays="true";
456 witherPrecision="true";
457 else
458 displayDays="false";
459 fi;
460 if $witherPrecision && [[ $precision -gt 0 ]]; then
461 displayDays="true";
462 fi;
463 if $witherPrecision; then ((precision--)); fi;
464
465 ### Hours
466 if $hasHours && [[ $precision -gt 0 ]]; then
467 displayHours="true";
468 witherPrecision="true";
469 else
470 displayHours="false";
471 fi;
472 if $witherPrecision && [[ $precision -gt 0 ]]; then
473 displayHours="true";
474 fi;
475 if $witherPrecision; then ((precision--)); fi;
476
477 ### Minutes
478 if $hasMinutes && [[ $precision -gt 0 ]]; then
479 displayMinutes="true";
480 witherPrecision="true";
481 else
482 displayMinutes="false";
483 fi;
484 if $witherPrecision && [[ $precision -gt 0 ]]; then
485 displayMinutes="true";
486 fi;
487 if $witherPrecision; then ((precision--)); fi;
488
489 ### Seconds
490
491 if $hasSeconds && [[ $precision -gt 0 ]]; then
492 displaySeconds="true";
493 witherPrecision="true";
494 else
495 displaySeconds="false";
496 fi;
497 if $witherPrecision && [[ $precision -gt 0 ]]; then
498 displaySeconds="true";
499 fi;
500 if $witherPrecision; then ((precision--)); fi;
501
502
503
504 ## Determine whether or not the "T" separator is needed to separate date and time elements
505 if ( $displayHours || $displayMinutes || $displaySeconds); then
506 displayDateTime="true"; else displayDateTime="false"; fi
507
508 ## Construct duration output string
509 OUTPUT="P"
510 if $displayYears; then
511 OUTPUT=$OUTPUT$fullYears"Y"; fi
512 if $displayMonths; then
513 OUTPUT=$OUTPUT$fullMonths"M"; fi
514 if $displayDays; then
515 OUTPUT=$OUTPUT$fullDays"D"; fi
516 if $displayDateTime; then
517 OUTPUT=$OUTPUT"T"; fi
518 if $displayHours; then
519 OUTPUT=$OUTPUT$fullHours"H"; fi
520 if $displayMinutes; then
521 OUTPUT=$OUTPUT$fullMinutes"M"; fi
522 if $displaySeconds; then
523 OUTPUT=$OUTPUT$fullSeconds"S"; fi
524
525 ## Output duration string to stdout
526 if [[ "$returnState" = "true" ]]; then echo "$OUTPUT"; fi
527
528 #===Determine function return code===
529 if [ "$returnState" = "true" ]; then
530 return 0;
531 else
532 echo "$returnState" 1>&2;
533 return 1;
534 fi
535
536 } # Get duration (ex: PT10M4S )
537 displayMissing() {
538 # Desc: Displays missing apps, files, and dirs
539 # Usage: displayMissing
540 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
541 # Output: stderr messages
542 #==BEGIN Display errors==
543 #===BEGIN Display Missing Apps===
544 missingApps="Missing apps :"
545 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
546 for key in "${!appRollCall[@]}"; do
547 value="${appRollCall[$key]}"
548 if [ "$value" = "false" ]; then
549 #echo "DEBUG:Missing apps: $key => $value";
550 missingApps="$missingApps""$key "
551 appMissing="true"
552 fi
553 done
554 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
555 echo "$missingApps" 1>&2;
556 fi
557 #===END Display Missing Apps===
558
559 #===BEGIN Display Missing Files===
560 missingFiles="Missing files:"
561 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
562 for key in "${!fileRollCall[@]}"; do
563 value="${fileRollCall[$key]}"
564 if [ "$value" = "false" ]; then
565 #echo "DEBUG:Missing files: $key => $value";
566 missingFiles="$missingFiles""$key "
567 fileMissing="true"
568 fi
569 done
570 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
571 echo "$missingFiles" 1>&2;
572 fi
573 #===END Display Missing Files===
574
575 #===BEGIN Display Missing Directories===
576 missingDirs="Missing dirs:"
577 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
578 for key in "${!dirRollCall[@]}"; do
579 value="${dirRollCall[$key]}"
580 if [ "$value" = "false" ]; then
581 #echo "DEBUG:Missing dirs: $key => $value";
582 missingDirs="$missingDirs""$key "
583 dirMissing="true"
584 fi
585 done
586 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
587 echo "$missingDirs" 1>&2;
588 fi
589 #===END Display Missing Directories===
590
591 #==END Display errors==
592 } # Display missing apps, files, dirs
593 setScriptTTL() {
594 #Desc: Sets script TTL
595 #Usage: setScriptTTL arg1
596 #Input: arg1: "day" or "hour"
597 #Output: scriptTTL
598 #Depends: timeUntilNextHour or timeUntilNextDay
599 local ARG1
600 ARG1="$1"
601 if [[ "$ARG1" = "day" ]]; then
602 # Set script lifespan to end at start of next day
603 if ! scriptTTL="$(timeUntilNextDay)"; then
604 if [[ "$scriptTTL" -eq 0 ]]; then
605 ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
606 else
607 yell "ERROR: timeUntilNextDay exit code $?"; exit 1;
608 fi;
609 fi;
610 elif [[ "$ARG1" = "hour" ]]; then
611 # Set script lifespan to end at start of next hour
612 if ! scriptTTL="$(timeUntilNextHour)"; then
613 if [[ "$scriptTTL" -eq 0 ]]; then
614 ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
615 else
616 yell "ERROR: timeUntilNextHour exit code $?"; exit 1;
617 fi;
618 fi;
619 else
620 yell "ERROR:Invalid argument for setScriptTTL function."; exit 1;
621 fi
622 } # Seconds until next (day|hour).
623 checkMakeTar() {
624 # Desc: Checks that a valid tar archive exists, creates one otherwise
625 # Usage: checkMakeTar [ path ]
626 # Version: 1.0.1
627 # Input: arg1: path of tar archive
628 # Output: exit code 0 : tar readable
629 # exit code 1 : tar missing; created
630 # exit code 2 : tar not readable; moved; replaced
631 # Depends: try, tar, date
632 local PATH_TAR returnFlag0 returnFlag1 returnFlag2
633 PATH_TAR="$1"
634
635 # Check if file is a valid tar archive
636 if tar --list --file="$PATH_TAR" 1>/dev/null 2>&1; then
637 ## T1: return success
638 returnFlag0="tar valid";
639 else
640 ## F1: Check if file exists
641 if [[ -f "$PATH_TAR" ]]; then
642 ### T: Rename file
643 try mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
644 returnFlag1="tar moved";
645 else
646 ### F: -
647 :
648 fi
649 ## F2: Create tar archive, return 0
650 try tar --create --file="$PATH_TAR" --files-from=/dev/null && \
651 returnFlag2="tar created";
652 fi
653
654 # Determine function return code
655 if [[ "$returnFlag0" = "tar valid" ]]; then
656 return 0;
657 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
658 return 1; # tar missing so created
659 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
660 return 2; # tar not readable so moved; replaced
661 fi
662 } # checks if arg1 is tar; creates one otherwise
663 appendArgTar(){
664 # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar
665 # Usage: writeArg "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
666 # Input: arg1: data to be written
667 # arg2: file name of file to be inserted into tar
668 # arg3: tar archive path (must exist first)
669 # arg4: temporary working dir
670 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
671 # Output: file written to disk
672 # Example: decrypt multiple large files in parallel
673 # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
674 # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
675 # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
676 # Depends: bash 5
677
678 # Save function name
679 local FN="${FUNCNAME[0]}"
680 yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
681
682 # Set file name
683 if ! [ -z "$2" ]; then FILENAME="$2"; else yell "ERROR:$FN:Not enough arguments."; exit 1; fi
684
685 # Check tar path is a file
686 if [ -f "$3" ]; then TAR_PATH="$3"; else yell "ERROR:$FN:Tar archive arg not a file."; exit 1; fi
687
688 # Check temp dir arg
689 if ! [ -z "$4" ]; then TMP_DIR="$4"; else yell "ERROR:$FN:No temporary working dir set."; exit 1; fi
690
691 # Set command strings
692 if ! [ -z "$5" ]; then CMD1="$5"; else CMD1="tee /dev/null "; fi # command string 1
693 if ! [ -z "$6" ]; then CMD2="$6"; else CMD2="tee /dev/null "; fi # command string 2
694 if ! [ -z "$7" ]; then CMD3="$7"; else CMD3="tee /dev/null "; fi # command string 3
695 if ! [ -z "$8" ]; then CMD4="$8"; else CMD4="tee /dev/null "; fi # command string 4
696
697 # Debug
698 yell "STATUS:$FN:CMD1:$CMD1"
699 yell "STATUS:$FN:CMD2:$CMD2"
700 yell "STATUS:$FN:CMD3:$CMD3"
701 yell "STATUS:$FN:CMD4:$CMD4"
702 yell "STATUS:$FN:FILENAME:$FILENAME"
703 yell "STATUS:$FN:TAR_PATH:$TAR_PATH"
704 yell "STATUS:$FN:TMP_DIR:$TMP_DIR"
705 # Write to temporary working dir
706 echo "$1" | $CMD1 | $CMD2 | $CMD3 | $CMD4 > "$TMP_DIR"/"$FILENAME";
707
708 # Append to tar
709 try tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
710 yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
711 } # Append Bash var to file appended to Tar archive
712 magicWriteVersion() {
713 # Desc: Appends time-stamped VERSION to PATHOUT_TAR
714 # Usage: magicWriteVersion
715 # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP
716 # Depends: dateTimeShort, appendArgTar
717
718 # Generate VERSION file in BashVar
719 FILEOUT_VERSION="$(dateTimeShort)..VERSION";
720 CONTENT_VERSION="$(dateTimeShort):$(basename "$0")"" Version:""$SCRIPT_VERSION";
721 # Create BashVar as file FILEOUT_VERSION and write-append to PATHOUT_TAR
722 appendArgTar "$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP";
723 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar()
724 magicWriteBuffer() {
725 # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR
726 # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP
727 # Depends: yell, try, vbm, appendArgTar, tar
728 local FN="${FUNCNAME[0]}";
729 wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
730 vbm "DEBUG:STATUS:$FN:Started magicWriteBuffer().";
731
732 # Validate PATHOUT_TAR as tar.
733 checkMakeTar "$PATHOUT_TAR";
734 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
735 if [[ $? -eq 1 ]] || [[ $? -eq 2 ]]; then magicWriteVersion; fi
736
737 # Write bufferBash to PATHOUT_TAR
738 appendArgTar "$bufferBash" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data
739 appendArgTar "$bufferBash" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file
740 appendArgTar "$bufferBash" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file
741 # Remove secured chunks from DIR_TMP
742 try rm "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML";
743 yell "DEBUG:STATUS:$FN:Finished magicWriteBuffer().";
744 } # bkgpslog write function
745
746 main() {
747 processArguments "$@" # Process arguments.
748
749 # Determine working directory
750 ## Set DIR_TMP_PARENT to user-specified value if specified
751 if [[ "$OPTION_TMPDIR" = "true" ]]; then
752 if [[ -d "$TMP_DIR_PRIORITY" ]]; then
753 DIR_TMP_PARENT="$OPTION_TMPDIR";
754 else
755 yell "WARNING:Specified temporary working directory not valid:$OPTION_TMPDIR";
756 exit 1;
757 fi
758 fi
759
760 ## Set DIR_TMP_PARENT to default or fallback otherwise
761 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
762 DIR_TMP_PARENT="$DIR_TMP_DEFAULT";
763 elif [[ -d /tmp ]]; then
764 yell "WARNING:/dev/shm not available. Falling back to /tmp .";
765 DIR_TMP_PARENT="/tmp";
766 else
767 yell "ERROR:No valid working directory available. Exiting.";
768 exit 1;
769 fi
770
771 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
772 DIR_TMP="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm "DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().
773
774 # Set output encryption and compression option strings
775 if [[ "$OPTION_ENCRYPT" = "true" ]]; then # Check if encryption option active.
776 if checkapp age; then # Check that age is available.
777 for pubkey in "${recPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
778 vbm "DEBUG:Testing pubkey string:$pubkey"
779 if echo "butts" | age -a -r "$pubkey" 1>/dev/null; then
780 # Form age recipient string
781 recipients="$recipients""-r $pubkey ";
782 vbm "STATUS:Added pubkey for forming age recipient string:""$pubkey";
783 vbm "DEBUG:recipients:""$recipients";
784 else
785 yell "ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
786 fi
787 done
788 vbm "DEBUG:Finished processing recPubKeys array";
789 # Form age command string
790 CMD_ENCRYPT="age ""$recipients ";
791 CMD_ENCRYPT_SUFFIX=".age";
792 else
793 yell "ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
794 fi
795 else
796 CMD_ENCRYPT="tee /dev/null ";
797 CMD_ENCRYPT_SUFFIX="";
798 vbm "DEBUG:Encryption not enabled."
799 fi
800 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
801 if checkapp gzip; then # Check if gzip available
802 CMD_COMPRESS="gzip ";
803 CMD_COMPRESS_SUFFIX=".gz";
804 else
805 yell "ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
806 fi
807 else
808 CMD_COMPRESS="tee /dev/null ";
809 CMD_COMPRESS_SUFFIX="";
810 vbm "DEBUG:Compression not enabled.";
811 fi
812
813 # Check that critical apps and dirs are available, displag missing ones.
814 if ! checkapp gpspipe tar && ! checkdir "$DIR_OUT" "/dev/shm"; then
815 yell "ERROR:Critical components missing.";
816 displayMissing; yell "Exiting."; exit 1; fi
817
818 # Set script lifespan
819 setScriptTTL "$SCRIPT_TTL"; # seconds until next new SCRIPT_TTL (ex: "day" or "hour")
820
821 # File name substring: encoded bufferTTL
822 bufferTTL_STR="$(timeDuration $BUFFER_TTL)";
823
824 # Init temp working dir
825 try mkdir "$DIR_TMP" && vbm "DEBUG:Working dir creatd at:$DIR_TMP";
826
827 # Initialize 'tar' archive
828 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
829 PATHOUT_TAR="$DIR_OUT"/"$(dateShort)".."$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar && \
830 vbm "STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
831 ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise.
832 checkMakeTar "$PATHOUT_TAR" && vbm "DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR";
833 ## Append VERSION file to PATHOUT_TAR
834 magicWriteVersion;
835
836 # Record gps data until script lifespan ends
837 declare debugCounter; debugCounter="0"; # set debug counter
838 while [[ "$SECONDS" -lt "$scriptTTL" ]]; do
839 timeBufferStart="$(dateTimeShort)"; # Note start time
840 # Determine file paths (time is start of buffer period)
841 FILEOUT_BASENAME="$timeBufferStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" && vbm "STATUS:Set FILEOUT_BASENAME to:$FILEOUT_BASENAME";
842 ## Files saved to DIR_TMP
843 FILEOUT_NMEA="$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_NMEA to:$FILEOUT_NMEA";
844 FILEOUT_GPX="$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_GPX to:$FILEOUT_GPX";
845 FILEOUT_KML="$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_KML to:$FILEOUT_KML";
846 PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_NMEA" && vbm "STATUS:Set PATHOUT_NMEA to:$PATHOUT_NMEA";
847 PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_GPX" && vbm "STATUS:Set PATHOUT_GPX to:$PATHOUT_GPX";
848 PATHOUT_KML="$DIR_TMP"/"$FILEOUT_KML" && vbm "STATUS:Set PATHOUT_KML to:$PATHOUT_KML";
849 ## Files saved to disk (DIR_OUT)
850 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
851 PATHOUT_TAR="$DIR_OUT"/"$(dateShort)".."$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar && \
852 vbm "STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
853 # Define GPS conversion commands
854 CMD_CONV_NMEA="tee /dev/null " && vbm "STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough
855 CMD_CONV_GPX="gpsbabel -i nmea -f - -o gpx -F - " && vbm "STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX
856 CMD_CONV_KML="gpsbabel -i nmea -f - -o kml -F - " && vbm "STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML
857 # Fill Bash variable buffer
858 bufferBash="$(timeout "$BUFFER_TTL""s" gpspipe -r)" && vbm "STATUS:Successfully filled bufferBash variable with gpspipe data."; # Record gpspipe nmea data to buffer for bufferTTL seconds
859 # Process bufferBash, save secured chunk set to DIR_TMP
860 vbm "STATUS:Beginning to save data to $DIR_TMP";
861 magicWriteBuffer &
862 # Append each secured chunk in memory dir (DIR_TMP) to file on disk (PATHOUT_TAR in DIR_OUT)
863 vbm "STATUS:DIR_TMP :$DIR_TMP";
864 vbm "STATUS:PATHOUT_TAR :$PATHOUT_TAR";
865 vbm "STATUS:PATHOUT_NMEA:$PATHOUT_NMEA";
866 vbm "STATUS:PATHOUT_GPX:$PATHOUT_GPX";
867 vbm "STATUS:PATHOUT_KML:$PATHOUT_KML";
868
869 # Reset buffer and filenames
870 unset bufferBash FILEOUT_BASENAME PATHOUT_NMEA PATHOUT_GPX PATHOUT_KML PATHOUT_TAR timeBufferStart;
871 vbm "DEBUG:Completed buffer session $debugCounter ." 1>&2;
872 ((debugCounter++))
873 done
874
875 # Remove DIR_TMP
876 try rm -r "$DIR_TMP";
877
878 vbm "STATUS:Main function finished.";
879 } # Main function.
880 #===END Declare local script functions===
881 #==END Define script parameters==
882
883
884 #==BEGIN Perform work and exit==
885 main "$@" # Run main function.
886 exit 0;
887 #==END Perform work and exit==