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