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