fix(bkgpslog):Recreate working dir if missing
[EVA-2020-02.git] / exec / bkgpslog
1 #!/bin/bash
2 # Desc: Records gps data
3
4 #==BEGIN Define script parameters==
5 ## Logging Behavior parameters
6 BUFFER_TTL="300"; # time between file writes
7 SCRIPT_TTL_TE="day"; # (day|hour)
8 #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option
9 DIR_TMP_DEFAULT="/dev/shm"; # Default parent of working directory
10
11 SCRIPT_TIME_START=$(date +%Y%m%dT%H%M%S.%N);
12 PATH="$HOME/.local/bin:$PATH"; # Add "$(systemd-path user-binaries)" path in case apps saved there
13 SCRIPT_HOSTNAME=$(hostname); # Save hostname of system running this script.
14 SCRIPT_VERSION="0.5.7"; # Define version of script.
15 SCRIPT_NAME="bkgpslog"; # Define basename of script file.
16 SCRIPT_URL="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script.
17 AGE_VERSION="1.0.0-beta2"; # Define version of age (encryption program)
18 AGE_URL="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age.
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 argRecPubKeys # for processArguments function
24 # declare -a errorHistory # for correcting buffer lag
25
26 ## Initialize variables
27 OPTION_VERBOSE=""; OPTION_ENCRYPT=""; OPTION_COMPRESS=""; OPTION_TMPDIR="";
28 errReset=0; BUFFER_TTL_ADJ_FLOAT="";
29
30 #===BEGIN Declare local script functions===
31 checkapp() {
32 # Desc: If arg is a command, save result in assoc array 'appRollCall'
33 # Usage: checkapp arg1 arg2 arg3 ...
34 # Input: global assoc. array 'appRollCall'
35 # Output: adds/updates key(value) to global assoc array 'appRollCall'
36 local returnState
37 #echo "DEBUG:$(date +%S.%N)..Starting checkapp function."
38 #echo "DEBUG:args: $@"
39 #echo "DEBUG:returnState:$returnState"
40
41 #===Process Args===
42 for arg in "$@"; do
43 #echo "DEBUG:processing arg:$arg"
44 if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
45 appRollCall[$arg]="true";
46 #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]}
47 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
48 else
49 appRollCall[$arg]="false"; returnState="false";
50 fi
51 done
52
53 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
54 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
55
56 #===Determine function return code===
57 if [ "$returnState" = "true" ]; then
58 #echo "DEBUG:checkapp returns true for $arg";
59 return 0;
60 else
61 #echo "DEBUG:checkapp returns false for $arg";
62 return 1;
63 fi
64 } # Check that app exists
65 checkfile() {
66 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
67 # Usage: checkfile arg1 arg2 arg3 ...
68 # Input: global assoc. array 'fileRollCall'
69 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
70 # Output: returns 0 if app found, 1 otherwise
71 local returnState
72
73 #===Process Args===
74 for arg in "$@"; do
75 #echo "DEBUG:processing arg:$arg"
76 if [ -f "$arg" ]; then
77 fileRollCall["$arg"]="true";
78 #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]}
79 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
80 else
81 fileRollCall["$arg"]="false"; returnState="false";
82 fi
83 done
84
85 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done
86 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
87
88 #===Determine function return code===
89 if [ "$returnState" = "true" ]; then
90 #echo "DEBUG:checkapp returns true for $arg";
91 return 0;
92 else
93 #echo "DEBUG:checkapp returns false for $arg";
94 return 1;
95 fi
96 } # Check that file exists
97 checkdir() {
98 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
99 # Usage: checkdir arg1 arg2 arg3 ...
100 # Input: global assoc. array 'dirRollCall'
101 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
102 # Output: returns 0 if app found, 1 otherwise
103 local returnState
104
105 #===Process Args===
106 for arg in "$@"; do
107 #echo "DEBUG:processing arg:$arg"
108 if [ -d "$arg" ]; then
109 dirRollCall["$arg"]="true";
110 #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]}
111 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
112 elif [ "$arg" = "" ]; then
113 dirRollCall["$arg"]="false"; returnState="false";
114 else
115 returnState="false";
116 fi
117 done
118
119 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done
120 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
121
122 #===Determine function return code===
123 if [ "$returnState" = "true" ]; then
124 #echo "DEBUG:checkapp returns true for $arg";
125 return 0;
126 else
127 #echo "DEBUG:checkapp returns false for $arg";
128 return 1;
129 fi
130 } # Check that dir exists
131
132 # Yell, Die, Try Three-Fingered Claw technique
133 # Ref/Attrib: https://stackoverflow.com/a/25515370
134 yell() { echo "$0: $*" >&2; }
135 die() { yell "$*"; exit 111; }
136 try() { "$@" || die "cannot $*"; }
137
138 echoerr() {
139 echo "$@" 1>&2; # Define stderr echo function.
140 } # Define stderr message function.
141 showUsage() {
142 echoerr "USAGE:"
143 echoerr " bkgpslog [ options ]"
144 echoerr
145 echoerr "OPTIONS:"
146 echoerr " -h, --help"
147 echoerr " Display help information."
148 echoerr " --version"
149 echoerr " Display script version."
150 echoerr " -v, --verbose"
151 echoerr " Display debugging info."
152 echoerr " -e, --encrypt"
153 echoerr " Encrypt output."
154 echoerr " -r, --recipient [ string pubkey ]"
155 echoerr " Specify recipient. May be age or ssh pubkey."
156 echoerr " May be specified multiple times for multiple pubkeys."
157 echoerr " See https://github.com/FiloSottile/age"
158 echoerr " -o, --output [ path dir ]"
159 echoerr " Specify output directory to save logs."
160 echoerr " -c, --compress"
161 echoerr " Compress output with gzip (before encryption if enabled)."
162 echoerr " -z, --time-zone"
163 echoerr " Specify time zone. (ex: \"America/New_York\")"
164 echoerr " -t, --temp-dir [path dir]"
165 echoerr " Specify parent directory for temporary working directory."
166 echoerr " Default: \"/dev/shm\""
167 echoerr " -R, --recipient-dir [path dir]"
168 echoerr " Specify directory containing files whose first lines are"
169 echoerr " to be interpreted as pubkey strings (see \\'-r\\' option)."
170 echoerr " -b, --buffer-ttl [integer]"
171 echoerr " Specify custom buffer period in seconds (default: 300 seconds)"
172 echoerr " -B, --script-ttl [integer]"
173 echoerr " Specify custom script time-to-live in seconds (default: \"day\")"
174 echoerr
175 echoerr "EXAMPLE: (bash script lines)"
176 echoerr "/bin/bash bkgpslog -v -e -c \\"
177 echoerr "-z \"UTC\" -t \"/dev/shm\" \\"
178 echoerr "-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
179 echoerr "-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
180 echoerr "-o ~/Sync/Location"
181 } # Display information on how to use this script.
182 showVersion() {
183 echoerr "$SCRIPT_VERSION"
184 } # Display script version.
185 vbm() {
186 # Usage: vbm "DEBUG:verbose message here"
187 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
188 # Input:
189 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
190 # - "$@" positional arguments fed to this function.
191 # Output: stderr
192 # Script function dependencies: echoerr
193 # External function dependencies: echo
194 # Last modified: 2020-04-11T23:57Z
195 # Last modified by: Steven Baltakatei Sandoval
196 # License: GPLv3+
197 # Ref./Attrib:
198
199 if [ "$OPTION_VERBOSE" = "true" ]; then
200 FUNCTION_TIME=$(date --iso-8601=ns); # Save current time in nano seconds.
201 echoerr "[$FUNCTION_TIME] ""$*"; # Display argument text.
202 fi
203
204 # End function
205 return 0; # Function finished.
206 } # Verbose message display function.
207 processArguments() {
208 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
209 #echoerr "DEBUG:Starting processArguments while loop."
210 #echoerr "DEBUG:Provided arguments are:""$*"
211 case "$1" in
212 -h | --help) showUsage; exit 1;; # Display usage.
213 --version) showVersion; exit 1;; # Show version
214 -v | --verbose) OPTION_VERBOSE="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode.
215 -o | --output) if [ -d "$2" ]; then DIR_OUT="$2"; vbm "DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory.
216 -e | --encrypt) OPTION_ENCRYPT="true"; vbm "DEBUG:Encrypted output mode enabled.";; # Enable encryption
217 -r | --recipient) OPTION_RECIPIENTS="true"; argRecPubKeys+=("$2"); vbm "STATUS:pubkey added:""$2"; shift;; # Add recipients
218 -c | --compress) OPTION_COMPRESS="true"; vbm "DEBUG:Compressed output mode enabled.";; # Enable compression
219 -z | --time-zone) try setTimeZoneEV "$2"; shift;; # Set timestamp timezone
220 -t | --temp-dir) OPTION_TMPDIR="true" && argTempDirPriority="$2"; shift;; # Set time zone
221 -R | --recipient-dir) OPTION_RECIPIENTS="true"; OPTION_RECDIR="true" && argRecDir="$2"; shift;; # Add recipient watch dir
222 -b | --buffer-ttl) OPTION_CUSTOM_BUFFERTTL="true" && argCustomBufferTTL="$2"; shift;; # Set custom buffer period (default: 300 seconds)
223 -B | --script-ttl) OPTION_CUSTOM_SCRIPTTTL_TE="true" && argCustomScriptTTL="$2"; shift;; # Set custom script TTL (default: "day")
224 *) echoerr "ERROR: Unrecognized argument: $1"; echoerr "STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
225 esac
226 shift
227 done
228 } # Argument Processing
229 setTimeZoneEV(){
230 # Desc: Set time zone environment variable TZ
231 # Usage: setTimeZoneEV arg1
232 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
233 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
234 # Output: exports TZ
235 # exit code 0 on success
236 # exit code 1 on incorrect number of arguments
237 # exit code 2 if unable to validate arg1
238 # Depends: yell, printenv, bash 5
239 # Tested on: Debian 10
240 ARG1="$1"
241 local tzDir returnState
242 if ! [[ $# -eq 1 ]]; then
243 yell "ERROR:Invalid argument count.";
244 return 1;
245 fi
246
247 # Read TZDIR env var if available
248 if printenv TZDIR 1>/dev/null 2>&1; then
249 tzDir="$(printenv TZDIR)";
250 else
251 tzDir="/usr/share/zoneinfo";
252 fi
253
254 # Validate TZ string
255 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
256 yell "ERROR:Invalid time zone argument.";
257 return 2;
258 else
259 # Export ARG1 as TZ environment variable
260 TZ="$ARG1" && export TZ && returnState="true";
261 fi
262
263 # Determine function return code
264 if [ "$returnState" = "true" ]; then
265 return 0;
266 fi
267 } # Exports TZ environment variable
268 timeUntilNextDay(){
269 # Desc: Report seconds until next day.
270 # Version: 1.0.0
271 # Output: stdout: integer seconds until next day
272 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
273 # Usage: timeUntilNextDay
274 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
275 # Depends: date 8, echo 8, yell, try
276
277 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
278
279 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
280 TIME_NEXT_DAY="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
281 SECONDS_UNTIL_NEXT_DAY="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
282 if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then
283 returnState="true";
284 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then
285 returnState="warning_zero";
286 yell "WARNING:Reported time until next day exactly zero.";
287 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then
288 returnState="warning_negative";
289 yell "WARNING:Reported time until next day is negative.";
290 fi
291
292 try echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
293
294 # Determine function return code
295 if [[ "$returnState" = "true" ]]; then
296 return 0;
297 elif [[ "$returnState" = "warning_zero" ]]; then
298 return 1;
299 elif [[ "$returnState" = "warning_negative" ]]; then
300 return 2;
301 fi
302 } # Report seconds until next day
303 timeUntilNextHour(){
304 # Desc: Report seconds until next hour
305 # Version 1.0.0
306 # Output: stdout: integer seconds until next hour
307 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
308 # Usage: timeUntilNextHour
309 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
310
311 local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
312 TIME_CURRENT="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
313 TIME_NEXT_HOUR="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
314 SECONDS_UNTIL_NEXT_HOUR="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second).
315 if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then
316 returnState="true";
317 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then
318 returnState="warning_zero";
319 yell "WARNING:Reported time until next hour exactly zero.";
320 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then
321 returnState="warning_negative";
322 yell "WARNING:Reported time until next hour is negative.";
323 fi
324
325 try echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
326
327 # Determine function return code
328 if [[ "$returnState" = "true" ]]; then
329 return 0;
330 elif [[ "$returnState" = "warning_zero" ]]; then
331 return 1;
332 elif [[ "$returnState" = "warning_negative" ]]; then
333 return 2;
334 fi
335 } # Report seconds until next hour
336 dateTimeShort(){
337 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
338 # Usage: dateTimeShort ([str date])
339 # Version 1.1.0
340 # Input: arg1: 'date'-parsable timestamp string (optional)
341 # Output: stdout: timestamp (ISO-8601, no separators)
342 # Depends: yell
343 local TIME_CURRENT TIME_CURRENT_SHORT argTime
344
345 argTime="$1";
346 # Get Current Time
347 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
348 # Decide to parse current or supplied date
349 ## Check if time argument empty
350 if [[ -z "$argTime" ]]; then
351 ## T: Time argument empty, use current time
352 TIME_INPUT="$TIME_CURRENT";
353 else
354 ## F: Time argument exists, validate time
355 if date --date="$argTime" 1>/dev/null 2>&1; then
356 ### T: Time argument is valid; use it
357 TIME_INPUT="$argTime";
358 else
359 ### F: Time argument not valid; exit
360 yell "ERROR:Invalid time argument supplied: \"$argTime\""; yell "Exiting."; exit 1;
361 fi
362 fi
363 # Construct and deliver separator-les date string
364 TIME_CURRENT_SHORT="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)";
365 echo "$TIME_CURRENT_SHORT";
366 } # Get YYYYmmddTHHMMSS±zzzz
367 dateShort(){
368 # Desc: Date without separators (YYYYmmdd)
369 # Usage: dateShort ([str date])
370 # Version: 1.1.0
371 # Input: arg1: 'date'-parsable timestamp string (optional)
372 # Output: stdout: date (ISO-8601, no separators)
373 # Depends: yell
374 local TIME_CURRENT DATE_CURRENT_SHORT
375
376 argTime="$1";
377 # Get Current Time
378 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
379 # Decide to parse current or supplied date
380 ## Check if time argument empty
381 if [[ -z "$argTime" ]]; then
382 ## T: Time argument empty, use current time
383 TIME_INPUT="$TIME_CURRENT";
384 else
385 ## F: Time argument exists, validate time
386 if date --date="$argTime" 1>/dev/null 2>&1; then
387 ### T: Time argument is valid; use it
388 TIME_INPUT="$argTime";
389 else
390 ### F: Time argument not valid; exit
391 yell "ERROR:Invalid time argument supplied: \"$argTime\""; yell "Exiting."; exit 1;
392 fi
393 fi
394 # Construct and deliver separator-les date string
395 DATE_CURRENT_SHORT="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
396 echo "$DATE_CURRENT_SHORT";
397 } # Get YYYYmmdd
398 timeDuration(){
399 # Desc: Given seconds, output ISO-8601 duration string
400 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
401 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
402 # Usage: timeDuration [1:seconds] ([2:precision])
403 # Version: 1.0.3
404 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
405 # arg2: precision level (optional; default=2)
406 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
407 # exit code 0: success
408 # exit code 1: error_input
409 # exit code 2: error_unknown
410 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
411 # Depends: date 8 (gnucoreutils), yell,
412 local returnState argSeconds argPrecision remainder precision witherPrecision
413 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
414 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
415 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
416
417 argSeconds="$1"; # read arg1 (seconds)
418 argPrecision="$2"; # read arg2 (precision)
419 precision=2; # set default precision
420
421 # Check that between one and two arguments is supplied
422 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
423 yell "ERROR:Invalid number of arguments:$# . Exiting.";
424 returnState="error_input"; fi
425
426 # Check that argSeconds provided
427 if [[ $# -ge 1 ]]; then
428 ## Check that argSeconds is a positive integer
429 if [[ "$argSeconds" =~ ^[[:digit:]]+$ ]]; then
430 :
431 else
432 yell "ERROR:argSeconds not a digit.";
433 returnState="error_input";
434 fi
435 else
436 yell "ERROR:No argument provided. Exiting.";
437 exit 1;
438 fi
439
440 # Consider whether argPrecision was provided
441 if [[ $# -eq 2 ]]; then
442 # Check that argPrecision is a positive integer
443 if [[ "$argPrecision" =~ ^[[:digit:]]+$ ]] && [[ "$argPrecision" -gt 0 ]]; then
444 precision="$argPrecision";
445 else
446 yell "ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
447 returnState="error_input";
448 fi;
449 else
450 :
451 fi;
452
453 remainder="$argSeconds" ; # seconds
454 ## Calculate full years Y, update remainder
455 fullYears=$(( remainder / (365*24*60*60) ));
456 remainder=$(( remainder - (fullYears*365*24*60*60) ));
457 ## Calculate full months M, update remainder
458 fullMonths=$(( remainder / (30*24*60*60) ));
459 remainder=$(( remainder - (fullMonths*30*24*60*60) ));
460 ## Calculate full days D, update remainder
461 fullDays=$(( remainder / (24*60*60) ));
462 remainder=$(( remainder - (fullDays*24*60*60) ));
463 ## Calculate full hours H, update remainder
464 fullHours=$(( remainder / (60*60) ));
465 remainder=$(( remainder - (fullHours*60*60) ));
466 ## Calculate full minutes M, update remainder
467 fullMinutes=$(( remainder / (60) ));
468 remainder=$(( remainder - (fullMinutes*60) ));
469 ## Calculate full seconds S, update remainder
470 fullSeconds=$(( remainder / (1) ));
471 remainder=$(( remainder - (remainder*1) ));
472 ## Check which fields filled
473 if [[ $fullYears -gt 0 ]]; then hasYears="true"; else hasYears="false"; fi
474 if [[ $fullMonths -gt 0 ]]; then hasMonths="true"; else hasMonths="false"; fi
475 if [[ $fullDays -gt 0 ]]; then hasDays="true"; else hasDays="false"; fi
476 if [[ $fullHours -gt 0 ]]; then hasHours="true"; else hasHours="false"; fi
477 if [[ $fullMinutes -gt 0 ]]; then hasMinutes="true"; else hasMinutes="false"; fi
478 if [[ $fullSeconds -gt 0 ]]; then hasSeconds="true"; else hasSeconds="false"; fi
479
480 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
481 witherPrecision="false"
482
483 ### Years
484 if $hasYears && [[ $precision -gt 0 ]]; then
485 displayYears="true";
486 witherPrecision="true";
487 else
488 displayYears="false";
489 fi;
490 if $witherPrecision; then ((precision--)); fi;
491
492 ### Months
493 if $hasMonths && [[ $precision -gt 0 ]]; then
494 displayMonths="true";
495 witherPrecision="true";
496 else
497 displayMonths="false";
498 fi;
499 if $witherPrecision && [[ $precision -gt 0 ]]; then
500 displayMonths="true";
501 fi;
502 if $witherPrecision; then ((precision--)); fi;
503
504 ### Days
505 if $hasDays && [[ $precision -gt 0 ]]; then
506 displayDays="true";
507 witherPrecision="true";
508 else
509 displayDays="false";
510 fi;
511 if $witherPrecision && [[ $precision -gt 0 ]]; then
512 displayDays="true";
513 fi;
514 if $witherPrecision; then ((precision--)); fi;
515
516 ### Hours
517 if $hasHours && [[ $precision -gt 0 ]]; then
518 displayHours="true";
519 witherPrecision="true";
520 else
521 displayHours="false";
522 fi;
523 if $witherPrecision && [[ $precision -gt 0 ]]; then
524 displayHours="true";
525 fi;
526 if $witherPrecision; then ((precision--)); fi;
527
528 ### Minutes
529 if $hasMinutes && [[ $precision -gt 0 ]]; then
530 displayMinutes="true";
531 witherPrecision="true";
532 else
533 displayMinutes="false";
534 fi;
535 if $witherPrecision && [[ $precision -gt 0 ]]; then
536 displayMinutes="true";
537 fi;
538 if $witherPrecision; then ((precision--)); fi;
539
540 ### Seconds
541
542 if $hasSeconds && [[ $precision -gt 0 ]]; then
543 displaySeconds="true";
544 witherPrecision="true";
545 else
546 displaySeconds="false";
547 fi;
548 if $witherPrecision && [[ $precision -gt 0 ]]; then
549 displaySeconds="true";
550 fi;
551 if $witherPrecision; then ((precision--)); fi;
552
553 ## Determine whether or not the "T" separator is needed to separate date and time elements
554 if ( $displayHours || $displayMinutes || $displaySeconds); then
555 displayDateTime="true"; else displayDateTime="false"; fi
556
557 ## Construct duration output string
558 OUTPUT="P"
559 if $displayYears; then
560 OUTPUT=$OUTPUT$fullYears"Y"; fi
561 if $displayMonths; then
562 OUTPUT=$OUTPUT$fullMonths"M"; fi
563 if $displayDays; then
564 OUTPUT=$OUTPUT$fullDays"D"; fi
565 if $displayDateTime; then
566 OUTPUT=$OUTPUT"T"; fi
567 if $displayHours; then
568 OUTPUT=$OUTPUT$fullHours"H"; fi
569 if $displayMinutes; then
570 OUTPUT=$OUTPUT$fullMinutes"M"; fi
571 if $displaySeconds; then
572 OUTPUT=$OUTPUT$fullSeconds"S"; fi
573
574 ## Output duration string to stdout
575 echo "$OUTPUT" && returnState="true";
576
577 #===Determine function return code===
578 if [ "$returnState" = "true" ]; then
579 return 0;
580 elif [ "$returnState" = "error_input" ]; then
581 yell "ERROR:input";
582 return 1;
583 else
584 yell "ERROR:Unknown";
585 return 2;
586 fi
587
588 } # Get duration (ex: PT10M4S )
589 displayMissing() {
590 # Desc: Displays missing apps, files, and dirs
591 # Usage: displayMissing
592 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
593 # Output: stderr messages
594 #==BEGIN Display errors==
595 #===BEGIN Display Missing Apps===
596 missingApps="Missing apps :"
597 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
598 for key in "${!appRollCall[@]}"; do
599 value="${appRollCall[$key]}"
600 if [ "$value" = "false" ]; then
601 #echo "DEBUG:Missing apps: $key => $value";
602 missingApps="$missingApps""$key "
603 appMissing="true"
604 fi
605 done
606 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
607 echo "$missingApps" 1>&2;
608 fi
609 #===END Display Missing Apps===
610
611 #===BEGIN Display Missing Files===
612 missingFiles="Missing files:"
613 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
614 for key in "${!fileRollCall[@]}"; do
615 value="${fileRollCall[$key]}"
616 if [ "$value" = "false" ]; then
617 #echo "DEBUG:Missing files: $key => $value";
618 missingFiles="$missingFiles""$key "
619 fileMissing="true"
620 fi
621 done
622 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
623 echo "$missingFiles" 1>&2;
624 fi
625 #===END Display Missing Files===
626
627 #===BEGIN Display Missing Directories===
628 missingDirs="Missing dirs:"
629 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
630 for key in "${!dirRollCall[@]}"; do
631 value="${dirRollCall[$key]}"
632 if [ "$value" = "false" ]; then
633 #echo "DEBUG:Missing dirs: $key => $value";
634 missingDirs="$missingDirs""$key "
635 dirMissing="true"
636 fi
637 done
638 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
639 echo "$missingDirs" 1>&2;
640 fi
641 #===END Display Missing Directories===
642
643 #==END Display errors==
644 } # Display missing apps, files, dirs
645 magicSetScriptTTL() {
646 #Desc: Sets script_TTL seconds from provided time_element string argument
647 #Usage: magicSetScriptTTL [str time_element]
648 #Input: arg1: string (Ex: SCRIPT_TTL_TE; "day" or "hour")
649 #Output: var: SCRIPT_TTL (integer seconds)
650 #Depends: timeUntilNextHour, timeUntilNextDay
651 local argTimeElement
652 argTimeElement="$1"
653 if [[ "$argTimeElement" = "day" ]]; then
654 # Set script lifespan to end at start of next day
655 if ! SCRIPT_TTL="$(timeUntilNextDay)"; then
656 if [[ "$SCRIPT_TTL" -eq 0 ]]; then
657 ((SCRIPT_TTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
658 else
659 yell "ERROR: timeUntilNextDay exit code $?"; exit 1;
660 fi;
661 fi;
662 elif [[ "$argTimeElement" = "hour" ]]; then
663 # Set script lifespan to end at start of next hour
664 if ! SCRIPT_TTL="$(timeUntilNextHour)"; then
665 if [[ "$SCRIPT_TTL" -eq 0 ]]; then
666 ((SCRIPT_TTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
667 else
668 yell "ERROR: timeUntilNextHour exit code $?"; exit 1;
669 fi;
670 fi;
671 else
672 yell "ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
673 fi
674 } # Seconds until next (day|hour).
675 checkMakeTar() {
676 # Desc: Checks that a valid tar archive exists, creates one otherwise
677 # Usage: checkMakeTar [ path ]
678 # Version: 1.0.1
679 # Input: arg1: path of tar archive
680 # Output: exit code 0 : tar readable
681 # exit code 1 : tar missing; created
682 # exit code 2 : tar not readable; moved; replaced
683 # Depends: try, tar, date
684 local PATH_TAR returnFlag0 returnFlag1 returnFlag2
685 PATH_TAR="$1"
686
687 # Check if file is a valid tar archive
688 if tar --list --file="$PATH_TAR" 1>/dev/null 2>&1; then
689 ## T1: return success
690 returnFlag0="tar valid";
691 else
692 ## F1: Check if file exists
693 if [[ -f "$PATH_TAR" ]]; then
694 ### T: Rename file
695 try mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
696 returnFlag1="tar moved";
697 else
698 ### F: -
699 :
700 fi
701 ## F2: Create tar archive, return 0
702 try tar --create --file="$PATH_TAR" --files-from=/dev/null && \
703 returnFlag2="tar created";
704 fi
705
706 # Determine function return code
707 if [[ "$returnFlag0" = "tar valid" ]]; then
708 return 0;
709 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
710 return 1; # tar missing so created
711 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
712 return 2; # tar not readable so moved; replaced
713 fi
714 } # checks if arg1 is tar; creates one otherwise
715 appendArgTar(){
716 # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar
717 # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
718 # Version: 1.0.3
719 # Input: arg1: data to be written
720 # arg2: file name of file to be inserted into tar
721 # arg3: tar archive path (must exist first)
722 # arg4: temporary working dir
723 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
724 # Output: file written to disk
725 # Example: decrypt multiple large files in parallel
726 # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
727 # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
728 # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
729 # Depends: bash 5
730 # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533
731
732 # Save function name
733 local FN="${FUNCNAME[0]}";
734 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
735
736 # Set file name
737 if ! [ -z "$2" ]; then FILENAME="$2"; else yell "ERROR:$FN:Not enough arguments."; exit 1; fi
738
739 # Check tar path is a file
740 if [ -f "$3" ]; then TAR_PATH="$3"; else yell "ERROR:$FN:Tar archive arg not a file."; exit 1; fi
741
742 # Check temp dir arg
743 if ! [ -z "$4" ]; then TMP_DIR="$4"; else yell "ERROR:$FN:No temporary working dir set."; exit 1; fi
744
745 # Set command strings
746 if ! [ -z "$5" ]; then CMD1="$5"; else CMD1="tee /dev/null "; fi # command string 1
747 if ! [ -z "$6" ]; then CMD2="$6"; else CMD2="tee /dev/null "; fi # command string 2
748 if ! [ -z "$7" ]; then CMD3="$7"; else CMD3="tee /dev/null "; fi # command string 3
749 if ! [ -z "$8" ]; then CMD4="$8"; else CMD4="tee /dev/null "; fi # command string 4
750
751 # Input command
752 CMD0="echo \"\$1\""
753
754 # # Debug
755 # yell "DEBUG:STATUS:$FN:CMD0:$CMD0"
756 # yell "DEBUG:STATUS:$FN:CMD1:$CMD1"
757 # yell "DEBUG:STATUS:$FN:CMD2:$CMD2"
758 # yell "DEBUG:STATUS:$FN:CMD3:$CMD3"
759 # yell "DEBUG:STATUS:$FN:CMD4:$CMD4"
760 # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME"
761 # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH"
762 # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR"
763
764 # Write to temporary working dir
765 eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME";
766
767 # Append to tar
768 try tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
769 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
770 } # Append Bash var to file appended to Tar archive
771 appendFileTar(){
772 # Desc: Processes first file and then appends to tar
773 # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
774 # Version: 1.0.2
775 # Input: arg1: path of file to be (processed and) written
776 # arg2: name to use for file inserted into tar
777 # arg3: tar archive path (must exist first)
778 # arg4: temporary working dir
779 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
780 # Output: file written to disk
781 # Example: decrypt multiple large files in parallel
782 # appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
783 # appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
784 # appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
785 # Depends: bash 5
786
787 # Save function name
788 local FN="${FUNCNAME[0]}";
789 #yell "DEBUG:STATUS:$FN:Finished appendFileTar()."
790
791 # Set file name
792 if ! [ -z "$2" ]; then FILENAME="$2"; else yell "ERROR:$FN:Not enough arguments."; exit 1; fi
793 # Check tar path is a file
794 if [ -f "$3" ]; then TAR_PATH="$3"; else yell "ERROR:$FN:Tar archive arg not a file."; exit 1; fi
795 # Check temp dir arg
796 if ! [ -z "$4" ]; then TMP_DIR="$4"; else yell "ERROR:$FN:No temporary working dir set."; exit 1; fi
797 # Set command strings
798 if ! [ -z "$5" ]; then CMD1="$5"; else CMD1="tee /dev/null "; fi # command string 1
799 if ! [ -z "$6" ]; then CMD2="$6"; else CMD2="tee /dev/null "; fi # command string 2
800 if ! [ -z "$7" ]; then CMD3="$7"; else CMD3="tee /dev/null "; fi # command string 3
801 if ! [ -z "$8" ]; then CMD4="$8"; else CMD4="tee /dev/null "; fi # command string 4
802
803 # Input command string
804 CMD0="cat \"\$1\""
805
806 # # Debug
807 # yell "DEBUG:STATUS:$FN:CMD0:$CMD0"
808 # yell "DEBUG:STATUS:$FN:CMD1:$CMD1"
809 # yell "DEBUG:STATUS:$FN:CMD2:$CMD2"
810 # yell "DEBUG:STATUS:$FN:CMD3:$CMD3"
811 # yell "DEBUG:STATUS:$FN:CMD4:$CMD4"
812 # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME"
813 # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH"
814 # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR"
815
816 # Write to temporary working dir
817 eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME";
818
819 # Append to tar
820 try tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
821 #yell "DEBUG:STATUS:$FN:Finished appendFileTar()."
822 } # Append file to Tar archive
823 checkAgePubkey() {
824 # Desc: Checks if string is an age-compatible pubkey
825 # Usage: checkAgePubkey [str pubkey]
826 # Version: 0.1.2
827 # Input: arg1: string
828 # Output: return code 0: string is age-compatible pubkey
829 # return code 1: string is NOT an age-compatible pubkey
830 # age stderr (ex: there is stderr if invalid string provided)
831 # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 )
832
833 argPubkey="$1";
834
835 if echo "test" | age -a -r "$argPubkey" 1>/dev/null; then
836 return 0;
837 else
838 return 1;
839 fi;
840 } # Check age pubkey
841 validateInput() {
842 # Desc: Validates Input
843 # Usage: validateInput [str input] [str input type]
844 # Version: 0.3.0
845 # Input: arg1: string to validate
846 # arg2: string specifying input type (ex:"ssh_pubkey")
847 # Output: return code 0: if input string matched specified string type
848 # Depends: bash 5, yell
849
850 # Save function name
851 local FN="${FUNCNAME[0]}";
852
853 # Process arguments
854 argInput="$1";
855 argType="$2";
856 if [[ $# -gt 2 ]]; then yell "ERROR:$0:$FN:Too many arguments."; exit 1; fi;
857
858 # Check for blank
859 if [[ -z "$argInput" ]]; then return 1; fi
860
861 # Define input types
862 ## ssh_pubkey
863 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
864 if [[ "$argType" = "ssh_pubkey" ]]; then
865 if [[ "$argInput" =~ ^[[:alnum:]-]*[\ ]*[[:alnum:]+/=]*$ ]]; then
866 return 0; fi; fi;
867
868 ## age_pubkey
869 ### Check for age1[:bech32:]
870 if [[ "$argType" = "age_pubkey" ]]; then
871 if [[ "$argInput" =~ ^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*$ ]]; then
872 return 0; fi; fi
873
874 ## integer
875 if [[ "$argType" = "integer" ]]; then
876 if [[ "$argInput" =~ ^[[:digit:]]*$ ]]; then
877 return 0; fi; fi;
878
879 ## time element (year, month, week, day, hour, minute, second)
880 if [[ "$argType" = "time_element" ]]; then
881 if [[ "$argInput" = "year" ]] || \
882 [[ "$argInput" = "month" ]] || \
883 [[ "$argInput" = "week" ]] || \
884 [[ "$argInput" = "day" ]] || \
885 [[ "$argInput" = "hour" ]] || \
886 [[ "$argInput" = "minute" ]] || \
887 [[ "$argInput" = "second" ]]; then
888 return 0; fi; fi;
889
890 # Return error if no condition matched.
891 return 1;
892 } # Validates strings
893 timeEpochNS() {
894 # Desc: Get epoch nanoseconds
895 # Usage: timeEpochNS
896 # Version 0.2.2
897 # Input: arg1: 'date'-parsable timestamp string (optional)
898 # Output: Nanoseconds since 1970-01-01
899 # Depends: date 8, yell()
900 # Ref/Attrib: Force base 10 Bash arith with '10#'. https://stackoverflow.com/a/24777667
901 local TIME_CURRENT TIME_INPUT TIME_EPOCH_FLOAT TIME_EPOCH_NSFRAC
902 local TIME_EPOCH_NS
903
904 argTime="$1";
905
906 # Get Current Time
907 TIME_CURRENT="$(date --iso-8601=ns)"; # Produce `date`-parsable current timestamp with resolution of 1 nanosecond.
908
909 # Decide to parse current or supplied time
910 ## Check if time argument empty
911 if [[ -z "$argTime" ]]; then
912 ## T: Time argument empty, use current time
913 TIME_INPUT="$TIME_CURRENT";
914 else
915 ## F: Time argument exists, validate time
916 if date --date="$argTime" 1>/dev/null 2>&1; then
917 ### T: Time argument is valid; use it
918 TIME_INPUT="$argTime";
919 else
920 ### F: Time argument not valid; exit
921 yell "ERROR:Invalid time argument supplied. Exiting."; exit 1;
922 fi;
923 fi;
924 # Construct and deliver nanoseconds since 1970-01-01
925 TIME_EPOCH_FLOAT="$(date --date="$TIME_INPUT" +%s.%N)"; # Save ssss.NNNNNNNNN
926 TIME_EPOCH_INT="$(echo "$TIME_EPOCH_FLOAT" | cut -d. -f1)"; # Get ssss
927 TIME_EPOCH_NSFRAC="$(echo "$TIME_EPOCH_FLOAT" | cut -d. -f2)"; # Get NNNNNNNNN
928 TIME_EPOCH_NS="$(( (10#"$TIME_EPOCH_INT" * 10**9) + (10#"$TIME_EPOCH_NSFRAC") ))";
929 echo "$TIME_EPOCH_NS";
930 } # Nanoseconds since 1970-01-01
931 magicBufferSleepPID() {
932 # Desc: Compensates for lag so buffer rounds start every BUFFER_TTL seconds
933 # Input: vars: BUFFER_TTL, errResetx10e3, K_P, T_I, T_D
934 # # Input: array: errorHistory
935 # Output: vars: BUFFER_TTL_ADJ_FLOAT
936 # Re/Attrib: https://en.wikipedia.org/wiki/PID_controller#Standard_versus_parallel_(ideal)_form
937 local BUFFER_TTL_NS
938 local timeBufferStartNS timeBufferStartNSExp errNS errNSx10e3
939 local errResetx10e3 errRatex10e3 ADJ BUFFER_TTL_ADJ_NS BUFFER_TTL_ADJ_INT
940 local BUFFER_TTL_ADJ_FLOATFRAC
941 # local errorHistorySize
942
943 # ## Define errorHistorySize
944 # errorHistorySize=100;
945 ## Define BUFFER_TTL in nanoseconds
946 BUFFER_TTL_NS=$((BUFFER_TTL * 10**9)) && vbm "BUFFER_TTL_NS:$BUFFER_TTL_NS";
947
948 ### PID Control factors
949 K_P=1; # Gain for compensating buffer round lag
950 T_I="$(((4)*BUFFER_TTL_NS/(1)))"; # Consider this number of past nanoseconds to eliminate error
951 T_D="$(((1)*BUFFER_TTL_NS/(1)))"; # Predict value this number of nanoseconds into the future
952
953 # Calculate Error, errNS, in nanoseconds
954 ## Get current time
955 timeBufferStartNS="$(timeEpochNS)" && vbm "timeBufferStartNS :$timeBufferStartNS";
956 ## Calculate expected time (from start time, current buffer round number, nominal BUFFER_TTL)
957 timeBufferStartNSExp="$(( (timeBufferFirstNS) + (BUFFER_TTL_NS * bufferRound) ))" && vbm "timeBufferStartNSExp:$timeBufferStartNSExp";
958 ## Calculate error (diff between timeBufferStartNSExp and timeBufferStartNS; usually negative)
959 errNS="$(( timeBufferStartNSExp - timeBufferStartNS ))" && vbm "errNS:$errNS";
960 # errNSx10e3="$((errNS*10**3))" && vbm "errNSx10e3:$errNSx10e3";
961 # ## Append error to errorHistory
962 # errorHistory+=("errNS");
963 # ### Trim errorHistory array if over errorHistorySize
964 # while [[ "${#errorHistory[@]}" -gt "errorHistorySize" ]]; then do
965 # unset "errorHistory[0]"; # remove oldest entry, creating sparse array
966 # errorHistory=("${errorHistory[@]}"); # reindex sparse array
967 # vbm "STATUS:Trimmed errorHistory array. Entry count:${#errorHistory[@]}";
968 # done;
969
970 # Calculate errReset in nanoseconds^2
971 ## errReset = int(errHistory(t),wrt(delta_BUFFER_TTL))
972 ## Integrate errorHistory with respect to time
973 # for value in "${errorHistory[@]}"; do
974 # errReset=$(( errReset + ( value*BUFFER_TTL_NS ) ));
975 # done;
976 vbm "errReset(orig):$errReset"
977 errReset="$(( (errReset + (errNS*BUFFER_TTL_NS)) ))" && vbm "errReset(post):$errReset";
978 # errResetx10e3="$(( ( errResetx10e3 + ( errNSx10e3 * BUFFER_TTL_NS ) )*10**3 ))" && vbm "errResetx10e3:$errResetx10e3";
979
980 # Calculate errRate in nanoseconds per nanosecond
981 errRate="$(( errNS / BUFFER_TTL_NS ))" && vbm "errRate:$errRate";
982 # errRatex10e3="$(( ( errNSx10e3 ) / BUFFER_TTL_NS ))" && vbm "errRatex10e3:$errRatex10e3";
983
984 # Debug
985 vbm "errNS :$errNS";
986 vbm "errResetTerm:$((errReset/T_I))";
987 vbm "errRateTerm :$((errRate*T_D))";
988
989 # Calculate PID control signal
990 ## ADJ = K_P * (errNS + errReset/T_I + errRate*T_D)
991 ADJ="$(( K_P*(errNS + errReset/T_I + errRate*T_D) ))" && vbm "ADJ:$ADJ";
992 # ADJ="$((K_P*(errNSx10e3 + (errResetx10e3/T_I) + (errRatex10e3*T_D) )/(10**3)))" && vbm "ADJ:$ADJ";
993
994 # Calculate BUFFER_TTL_ADJ_FLOAT from ADJ (ns)
995 ## Calculate BUFFER_TTL_ADJ in nanoseconds (BUFFER_TTL_ADJ_NS = BUFFER_TTL_NS + ADJ)
996 BUFFER_TTL_ADJ_NS="$((BUFFER_TTL_NS + ADJ))" && vbm "BUFFER_TTL_ADJ_NS:$BUFFER_TTL_ADJ_NS";
997 ## Calculate integer seconds
998 BUFFER_TTL_ADJ_INT="$((BUFFER_TTL_ADJ_NS/(10**9)))" && vbm "BUFFER_TTL_ADJ_INT:$BUFFER_TTL_ADJ_INT";
999 ### Catch negative integer seconds, set minimum of BUFFER_TTL/10 seconds
1000 if [[ "$BUFFER_TTL_ADJ_INT" -le "$((BUFFER_TTL/10))" ]]; then
1001 BUFFER_TTL_ADJ_INT="$((BUFFER_TTL/10))";
1002 yell "WARNING:Buffer lag adjustment yielded negative seconds.";
1003 fi;
1004 ## Calculate nanosecond remainder
1005 ### Remove integer
1006 BUFFER_TTL_ADJ_FLOATFRAC="$((BUFFER_TTL_ADJ_NS - (BUFFER_TTL_ADJ_INT*(10**9)) ))" && vbm "BUFFER_TTL_ADJ_FLOATFRAC:$BUFFER_TTL_ADJ_FLOATFRAC";
1007 ### Calc absolute value of fraction (by removing '-' if present; see https://stackoverflow.com/a/47240327
1008 BUFFER_TTL_ADJ_FLOATFRAC="${BUFFER_TTL_ADJ_FLOATFRAC#-}" && vbm "BUFFER_TTL_ADJ_FLOATFRAC:$BUFFER_TTL_ADJ_FLOATFRAC";
1009 ## Form float BUFFER_TTL_ADJ_FLOAT
1010 BUFFER_TTL_ADJ_FLOAT="$BUFFER_TTL_ADJ_INT"."$BUFFER_TTL_ADJ_FLOATFRAC" && vbm "BUFFER_TTL_ADJ_FLOAT:$BUFFER_TTL_ADJ_FLOAT";
1011 vbm "STATUS:Calculated adjusted BUFFER_TTL (seconds):$BUFFER_TTL_ADJ_FLOAT";
1012 } # Calc BUFFER_TTL_ADJ_FLOAT so buffer starts every BUFFER_TTL seconds
1013 magicWriteVersion() {
1014 # Desc: Appends time-stamped VERSION to PATHOUT_TAR
1015 # Usage: magicWriteVersion
1016 # Version: 0.1.0
1017 # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP
1018 # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME
1019 # Output: appends tar PATHOUT_TAR
1020 # Depends: dateTimeShort, appendArgTar
1021 local CONTENT_VERSION pubKeyIndex
1022
1023 # Set VERSION file name
1024 FILEOUT_VERSION="$(dateTimeShort)..VERSION";
1025
1026 # Gather VERSION data in CONTENT_VERSION
1027 CONTENT_VERSION="SCRIPT_VERSION=$SCRIPT_VERSION";
1028 #CONTENT_VERSION="$CONTENT_VERSION""\\n";
1029 CONTENT_VERSION="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME";
1030 CONTENT_VERSION="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL";
1031 CONTENT_VERSION="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION";
1032 CONTENT_VERSION="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL";
1033 CONTENT_VERSION="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)";
1034 CONTENT_VERSION="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME";
1035 ## Add list of recipient pubkeys
1036 for pubkey in "${recPubKeysValid[@]}"; do
1037 ((pubKeyIndex++))
1038 CONTENT_VERSION="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1039 done
1040 ## Process newline escapes
1041 CONTENT_VERSION="$(echo -e "$CONTENT_VERSION")"
1042
1043 # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR
1044 appendArgTar "$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP";
1045
1046 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar()
1047 magicGatherWriteBuffer() {
1048 # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR
1049 # Inputs: vars: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP,
1050 # Inputs: vars: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX
1051 # Output: file: (PATHOUT_TAR)
1052 # Depends: yell(), try(), vbm(), appendArgTar(), tar 1, sleep 8, checkMakeTar()
1053 # Depends: magicWriteVersion(), appendFileTar()
1054 local FN
1055
1056 vbm "DEBUG:STATUS:$FN:Started magicGatherWriteBuffer().";
1057 # Debug:Get function name
1058 FN="${FUNCNAME[0]}";
1059
1060 # Create buffer file with unique name
1061 PATHOUT_BUFFER="$DIR_TMP/buffer$SECONDS" && vbm "PATHOUT_BUFFER:$PATHOUT_BUFFER";
1062 # Fill buffer
1063 timeout "$BUFFER_TTL"s gpspipe -r -o "$PATHOUT_BUFFER" ;
1064 timeBufferStartLong="$(date --date="$BUFFER_TTL seconds ago" --iso-8601=seconds)" && vbm "timeBufferStartLong:$timeBufferStartLong" || yell "ERROR:timeBufferStartLong fail";
1065 timeBufferStart="$(dateTimeShort "$timeBufferStartLong" )" && vbm "timeBufferStart:$timeBufferStart" || yell "ERROR:timeBufferStart fail"; # Note start time
1066 # Determine file paths (time is start of buffer period)
1067 FILEOUT_BASENAME="$timeBufferStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" && vbm "STATUS:Set FILEOUT_BASENAME to:$FILEOUT_BASENAME";
1068 ## Files saved to DIR_TMP
1069 FILEOUT_NMEA="$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_NMEA to:$FILEOUT_NMEA";
1070 FILEOUT_GPX="$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_GPX to:$FILEOUT_GPX";
1071 FILEOUT_KML="$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_KML to:$FILEOUT_KML";
1072 PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_NMEA" && vbm "STATUS:Set PATHOUT_NMEA to:$PATHOUT_NMEA";
1073 PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_GPX" && vbm "STATUS:Set PATHOUT_GPX to:$PATHOUT_GPX";
1074 PATHOUT_KML="$DIR_TMP"/"$FILEOUT_KML" && vbm "STATUS:Set PATHOUT_KML to:$PATHOUT_KML";
1075 ## Files saved to disk (DIR_OUT)
1076 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
1077 PATHOUT_TAR="$DIR_OUT"/"$(dateShort "$(date --date="$BUFFER_TTL seconds ago" --iso-8601=seconds)")".."$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar && \
1078 vbm "STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
1079 # DEBUG: check vars
1080 vbm "STATUS:FN :$FN";
1081 vbm "STATUS:DIR_TMP :$DIR_TMP";
1082 vbm "STATUS:PATHOUT_TAR :$PATHOUT_TAR";
1083 vbm "STATUS:PATHOUT_NMEA :$PATHOUT_NMEA";
1084 vbm "STATUS:PATHOUT_GPX :$PATHOUT_GPX";
1085 vbm "STATUS:PATHOUT_KML :$PATHOUT_KML";
1086 vbm "STATUS:BUFFER_TTL :$BUFFER_TTL";
1087 vbm "STATUS:PATHOUT_BUFFER :$PATHOUT_BUFFER";
1088 vbm "STATUS:timeBufferStart:$timeBufferStart";
1089 vbm "FILEOUT_BASENAME :$FILEOUT_BASENAME";
1090
1091
1092 # Validate PATHOUT_TAR as tar.
1093 checkMakeTar "$PATHOUT_TAR";
1094 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
1095 vbm "exit status before magicWriteVersion:$?"
1096 if [[ $? -eq 1 ]] || [[ $? -eq 2 ]]; then magicWriteVersion; fi
1097
1098 # Write bufferBash to PATHOUT_TAR
1099 wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
1100 appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data
1101 appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file
1102 appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file
1103
1104 # Remove secured chunks from DIR_TMP
1105 rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML";
1106 vbm "DEBUG:STATUS:$FN:Finished magicGatherWriteBuffer().";
1107 } # write buffer to disk
1108 magicParseRecipientDir() {
1109 # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory")
1110 # Inputs: vars: OPTION_RECDIR, argRecDir, OPTION_ENCRYPT
1111 # arry: recPubKeysValid
1112 # Outputs: arry: recPubKeysValid
1113 # Depends: processArguments,
1114 local recFileLine updateRecipients recipientDir
1115 declare -a candRecPubKeysValid
1116
1117 # Check that '-e' and '-R' set
1118 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then
1119 ### Check that argRecDir is a directory.
1120 if [[ -d "$argRecDir" ]]; then
1121 recipientDir="$argRecDir" && vbm "STATUS:Recipient watch directory detected:\"$recipientDir\"";
1122 #### Initialize variable indicating outcome of pubkey review
1123 unset updateRecipients
1124 #### Add existing recipients
1125 candRecPubKeysValid=("${recPubKeysValidStatic[@]}");
1126 #### Parse files in recipientDir
1127 for file in "$recipientDir"/*; do
1128 ##### Read first line of each file
1129 recFileLine="$(head -n1 "$file")" && vbm "STATUS:Checking if pubkey:\"$recFileLine\"";
1130 ##### check if first line is a valid pubkey
1131 if checkAgePubkey "$recFileLine" && \
1132 ( validateInput "$recFileLine" "ssh_pubkey" || validateInput "$recFileLine" "age_pubkey"); then
1133 ###### T: add candidate pubkey to candRecPubKeysValid
1134 candRecPubKeysValid+=("$recFileLine") && vbm "STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\"";
1135 else
1136 ###### F: throw warning;
1137 yell "ERROR:Invalid recipient file detected. Not modifying recipient list."
1138 updateRecipients="false";
1139 fi;
1140 done
1141 #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected
1142 if ! [[ "$updateRecipients" = "false" ]]; then
1143 recPubKeysValid=("${candRecPubKeysValid[@]}") && vbm "STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\"";
1144 fi;
1145 else
1146 yell "ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
1147 fi;
1148 fi;
1149 # Handle case if '-R' set but '-e' not set
1150 if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then
1151 yell "ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi;
1152 } # Update recPubKeysValid with argRecDir
1153 magicParseRecipientArgs() {
1154 # Desc: Parses recipient arguments specified by '-r' option
1155 # Input: vars: OPTION_ENCRYPT from processArguments()
1156 # arry: argRecPubKeys from processArguments()
1157 # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX
1158 # arry: recPubKeysValid, recPubKeysValidStatic
1159 # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments()
1160 local recipients
1161
1162 # Check if encryption option active.
1163 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then
1164 if checkapp age; then # Check that age is available.
1165 for pubkey in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
1166 vbm "DEBUG:Testing pubkey string:$pubkey";
1167 if checkAgePubkey "$pubkey" && \
1168 ( validateInput "$pubkey" "ssh_pubkey" || validateInput "$pubkey" "age_pubkey"); then
1169 #### Form age recipient string
1170 recipients="$recipients""-r '$pubkey' ";
1171 vbm "STATUS:Added pubkey for forming age recipient string:""$pubkey";
1172 vbm "DEBUG:recipients:""$recipients";
1173 #### Add validated pubkey to recPubKeysValid array
1174 recPubKeysValid+=("$pubkey") && vbm "DEBUG:recPubkeysValid:pubkey added:$pubkey";
1175 else
1176 yell "ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
1177 fi;
1178 done
1179 vbm "DEBUG:Finished processing argRecPubKeys array";
1180 vbm "STATUS:Array of validated pubkeys:${recPubKeysValid[*]}";
1181 recPubKeysValidStatic=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function
1182
1183 ## Form age command string
1184 CMD_ENCRYPT="age ""$recipients " && vbm "CMD_ENCRYPT:$CMD_ENCRYPT";
1185 CMD_ENCRYPT_SUFFIX=".age" && vbm "CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX";
1186 else
1187 yell "ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
1188 fi;
1189 else
1190 CMD_ENCRYPT="tee /dev/null " && vbm "CMD_ENCRYPT:$CMD_ENCRYPT";
1191 CMD_ENCRYPT_SUFFIX="" && vbm "CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX";
1192 vbm "DEBUG:Encryption not enabled."
1193 fi;
1194 # Catch case if '-e' is set but '-r' or '-R' is not
1195 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECIPIENTS" = "true" ]]; then
1196 yell "ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
1197 # Catch case if '-r' or '-R' set but '-e' is not
1198 if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then
1199 yell "ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
1200 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
1201 magicParseCompressionArg() {
1202 # Desc: Parses compression arguments specified by '-c' option
1203 # Input: vars: OPTION_COMPRESS
1204 # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX
1205 # Depends: checkapp(), vbm(), gzip,
1206 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
1207 if checkapp gzip; then # Check if gzip available
1208 CMD_COMPRESS="gzip " && vbm "CMD_COMPRESS:$CMD_COMPRESS";
1209 CMD_COMPRESS_SUFFIX=".gz" && vbm "CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX";
1210 else
1211 yell "ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
1212 fi
1213 else
1214 CMD_COMPRESS="tee /dev/null " && vbm "CMD_COMPRESS:$CMD_COMPRESS";
1215 CMD_COMPRESS_SUFFIX="" && vbm "CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX";
1216 vbm "DEBUG:Compression not enabled.";
1217 fi
1218 } # Form compression cmd string and filename suffix
1219 magicInitWorkingDir() {
1220 # Desc: Determine temporary working directory from defaults or user input
1221 # Usage: magicInitWorkignDir
1222 # Input: vars: OPTION_TEMPDIR, argTempDirPriority, DIR_TMP_DEFAULT
1223 # Input: vars: SCRIPT_TIME_START
1224 # Output: vars: DIR_TMP
1225 # Depends: processArguments(), vbm(), yell()
1226 # Parse '-t' option (user-specified temporary working dir)
1227 ## Set DIR_TMP_PARENT to user-specified value if specified
1228 local DIR_TMP_PARENT
1229
1230 if [[ "$OPTION_TMPDIR" = "true" ]]; then
1231 if [[ -d "$argTempDirPriority" ]]; then
1232 DIR_TMP_PARENT="$argTempDirPriority";
1233 else
1234 yell "WARNING:Specified temporary working directory not valid:$argTempDirPriority";
1235 exit 1; # Exit since user requires a specific temp dir and it is not available.
1236 fi;
1237 else
1238 ## Set DIR_TMP_PARENT to default or fallback otherwise
1239 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
1240 DIR_TMP_PARENT="$DIR_TMP_DEFAULT";
1241 elif [[ -d /tmp ]]; then
1242 yell "WARNING:$DIR_TMP_DEFAULT not available. Falling back to /tmp .";
1243 DIR_TMP_PARENT="/tmp";
1244 else
1245 yell "ERROR:No valid working directory available. Exiting.";
1246 exit 1;
1247 fi
1248 fi;
1249 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
1250 DIR_TMP="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm "DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().
1251 } # Sets working dir
1252 magicParseCustomTTL() {
1253 # Desc: Set user-specified TTLs for buffer and script
1254 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
1255 # Input: vars: OPTION_CUSTOM_BUFFERTTL, OPTION_CUSTOM_SCRIPTTTL
1256 # Input: vars: BUFFER_TTL (integer), SCRIPT_TTL_TE (string)
1257 # Output: BUFFER_TTL (integer), SCRIPT_TTL_TE (string)
1258 # Depends validateInput(), showUsage(), yell
1259
1260 # React to '-b, --buffer-ttl' option
1261 if [[ "$OPTION_CUSTOM_BUFFERTTL" = "true" ]]; then
1262 ## T: Check if argCustomBufferTTL is an integer
1263 if validateInput "$argCustomBufferTTL" "integer"; then
1264 ### T: argCustomBufferTTL is an integer
1265 BUFFER_TTL="$argCustomBufferTTL" && vbm "Custom BUFFER_TTL from -b:$BUFFER_TTL";
1266 else
1267 ### F: argcustomBufferTTL is not an integer
1268 yell "ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage; exit 1;
1269 fi;
1270 ## F: do not change BUFFER_TTL
1271 fi;
1272
1273 # React to '-B, --script-ttl' option
1274 if [[ "$OPTION_CUSTOM_SCRIPTTTL_TE" = "true" ]]; then
1275 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
1276 if validateInput "$argCustomScriptTTL" "time_element"; then
1277 ### T: argCustomScriptTTL is a time element
1278 SCRIPT_TTL_TE="$argCustomScriptTTL" && vbm "Custom SCRIPT_TTL_TE from -B:$SCRIPT_TTL_TE";
1279 else
1280 ### F: argcustomScriptTTL is not a time element
1281 yell "ERROR:Invalid time element argument for custom script time-to-live."; showUsage; exit 1;
1282 fi;
1283 ## F: do not change SCRIPT_TTL_TE
1284 fi;
1285 } # Sets custom script or buffer TTL if specified
1286
1287
1288 main() {
1289 # DEBUG: Print environment variables
1290 vbm "echo $(printenv)";
1291 # Process arguments
1292 processArguments "$@";
1293 ## Act upon arguments
1294 ### Determine working directory
1295 magicInitWorkingDir; # Sets DIR_TMP from argTempDirPriority
1296 ### Set output encryption and compression option strings
1297 #### React to "-r" ("encryption recipients") option
1298 magicParseRecipientArgs; # Updates recPubKeysValid, CMD_ENCRYPT[_SUFFIX] from argRecPubKeys
1299 #### React to "-c" ("compression") option
1300 magicParseCompressionArg; # Updates CMD_COMPRESS[_SUFFIX]
1301 #### React to "-R" ("recipient directory") option
1302 magicParseRecipientDir; # Updates recPubKeysValid
1303 #### React to custom buffer and script TTL options ("-b", "-B")
1304 magicParseCustomTTL; # Sets custom SCRIPT_TTL_TE and/or BUFFER_TTL if specified
1305
1306 # Check that critical apps and dirs are available, display missing ones.
1307 if ! checkapp gpspipe tar && ! checkdir "$DIR_OUT" "DIR_TMP"; then
1308 yell "ERROR:Critical components missing.";
1309 displayMissing; yell "Exiting."; exit 1; fi
1310
1311 # Set script lifespan (SCRIPT_TTL from SCRIPT_TTL_TE)
1312 magicSetScriptTTL "$SCRIPT_TTL_TE";
1313 ## Note: SCRIPT_TTL_TE is time element string (ex: "day") while SCRIPT_TTL is integer seconds
1314
1315 # File name substring (ISO-8601 duration from BUFFER_TTL)
1316 bufferTTL_STR="$(timeDuration "$BUFFER_TTL")" && vbm "DEBUG:bufferTTL_STR:$bufferTTL_STR";
1317
1318 # Init temp working dir
1319 try mkdir "$DIR_TMP" && vbm "DEBUG:Working dir created at DIR_TMP:$DIR_TMP";
1320
1321 # Initialize 'tar' archive
1322 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
1323 PATHOUT_TAR="$DIR_OUT"/"$(dateShort)".."$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar && \
1324 vbm "STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
1325 ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise.
1326 checkMakeTar "$PATHOUT_TAR" && vbm "DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR";
1327 ## Append VERSION file to PATHOUT_TAR
1328 magicWriteVersion;
1329
1330 # Define GPS conversion commands
1331 CMD_CONV_NMEA="tee /dev/null " && vbm "STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough
1332 CMD_CONV_GPX="gpsbabel -i nmea -f - -o gpx -F - " && vbm "STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX
1333 CMD_CONV_KML="gpsbabel -i nmea -f - -o kml -F - " && vbm "STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML
1334
1335 # MAIN LOOP:Record gps data until script lifespan ends
1336 timeBufferFirstNS="$(timeEpochNS)"; bufferRound=0; BUFFER_TTL_ADJ_FLOAT="$BUFFER_TTL";
1337 while [[ "$SECONDS" -lt "$SCRIPT_TTL" ]]; do
1338 if ! [[ -d "$DIR_TMP" ]]; then yell "ERROR:DIR_TMP existence failure:$DIR_TMP"; try mkdir "$DIR_TMP" && vbm "DEBUG:Working dir recreated DIR_TMP:$DIR_TMP"; fi
1339 magicParseRecipientDir;
1340 magicGatherWriteBuffer &
1341 sleep "$BUFFER_TTL_ADJ_FLOAT"; # adjusted by magicBufferSleepPID
1342 ((bufferRound++));
1343 magicBufferSleepPID; # Calculates BUFFER_TTL_ADJ_FLOAT from BUFFER_TTL given buffer expected start time vs. actual
1344 done
1345
1346 # Cleanup
1347 ## Remove DIR_TMP
1348 try rm -r "$DIR_TMP" && vbm "Removed DIR_TMP:$DIR_TMP";
1349
1350 vbm "STATUS:Main function finished.";
1351 } # Main function.
1352 #===END Declare local script functions===
1353 #==END Define script parameters==
1354
1355
1356 #==BEGIN Perform work and exit==
1357 main "$@" # Run main function.
1358 exit 0;
1359 #==END Perform work and exit==
1360
1361 # Author: Steven Baltakatei Sandoval;
1362 # License: GPLv3+