2 # Desc: Records gps data
4 #==BEGIN Define script parameters==
5 ## Logging Behavior parameters
6 bufferTTL
="300"; # time between file writes
7 scriptTTL_TE
="day"; # (day|hour)
8 #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option
9 dirTmpDefault
="/dev/shm"; # Default parent of working directory
11 scriptTimeStart
=$
(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 scriptHostname
=$
(hostname
); # Save hostname of system running this script.
14 scriptVersion
="0.5.8"; # Define version of script.
15 scriptName
="bkgpslog"; # Define basename of script file.
16 scriptURL
="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script.
17 ageVersion
="1.0.0-beta2"; # Define version of age (encryption program)
18 ageURL
="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age.
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
26 ## Initialize variables
27 optionVerbose
=""; optionEncrypt
=""; optionCompress
=""; optionTmpDir
="";
28 errReset
=0; bufferTTL_AdjFloat
=""; scriptTTL
="";
30 #===BEGIN Declare local script functions===
31 yell
() { echo "$0: $*" >&2; } #o Yell, Die, Try Three-Fingered Claw technique
32 die
() { yell
"$*"; exit 111; } #o Ref/Attrib: https://stackoverflow.com/a/25515370
33 try
() { "$@" || die
"cannot $*"; } #o
35 # Desc: If arg is a command, save result in assoc array 'appRollCall'
36 # Usage: checkapp arg1 arg2 arg3 ...
37 # Input: global assoc. array 'appRollCall'
38 # Output: adds/updates key(value) to global assoc array 'appRollCall'
40 #echo "DEBUG:$(date +%S.%N)..Starting checkapp function."
41 #echo "DEBUG:args: $@"
42 #echo "DEBUG:returnState:$returnState"
46 #echo "DEBUG:processing arg:$arg"
47 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
48 appRollCall
[$arg]="true";
49 #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]}
50 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
52 appRollCall
[$arg]="false"; returnState
="false";
56 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
57 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
59 #===Determine function return code===
60 if [ "$returnState" = "true" ]; then
61 #echo "DEBUG:checkapp returns true for $arg";
64 #echo "DEBUG:checkapp returns false for $arg";
67 } # Check that app exists
69 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
70 # Usage: checkfile arg1 arg2 arg3 ...
71 # Input: global assoc. array 'fileRollCall'
72 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
73 # Output: returns 0 if app found, 1 otherwise
78 #echo "DEBUG:processing arg:$arg"
79 if [ -f "$arg" ]; then
80 fileRollCall
["$arg"]="true";
81 #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]}
82 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
84 fileRollCall
["$arg"]="false"; returnState
="false";
88 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done
89 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
91 #===Determine function return code===
92 if [ "$returnState" = "true" ]; then
93 #echo "DEBUG:checkapp returns true for $arg";
96 #echo "DEBUG:checkapp returns false for $arg";
99 } # Check that file exists
101 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
102 # Usage: checkdir arg1 arg2 arg3 ...
103 # Input: global assoc. array 'dirRollCall'
104 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
105 # Output: returns 0 if app found, 1 otherwise
110 #echo "DEBUG:processing arg:$arg"
111 if [ -d "$arg" ]; then
112 dirRollCall
["$arg"]="true";
113 #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]}
114 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
115 elif [ "$arg" = "" ]; then
116 dirRollCall
["$arg"]="false"; returnState
="false";
122 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done
123 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
125 #===Determine function return code===
126 if [ "$returnState" = "true" ]; then
127 #echo "DEBUG:checkapp returns true for $arg";
130 #echo "DEBUG:checkapp returns false for $arg";
133 } # Check that dir exists
135 echo "$@" 1>&2; # Define stderr echo function.
136 } # Define stderr message function.
139 echoerr
" bkgpslog [ options ]"
142 echoerr
" -h, --help"
143 echoerr
" Display help information."
145 echoerr
" Display script version."
146 echoerr
" -v, --verbose"
147 echoerr
" Display debugging info."
148 echoerr
" -e, --encrypt"
149 echoerr
" Encrypt output."
150 echoerr
" -r, --recipient [ string pubkey ]"
151 echoerr
" Specify recipient. May be age or ssh pubkey."
152 echoerr
" May be specified multiple times for multiple pubkeys."
153 echoerr
" See https://github.com/FiloSottile/age"
154 echoerr
" -o, --output [ path dir ]"
155 echoerr
" Specify output directory to save logs."
156 echoerr
" -c, --compress"
157 echoerr
" Compress output with gzip (before encryption if enabled)."
158 echoerr
" -z, --time-zone"
159 echoerr
" Specify time zone. (ex: \"America/New_York\")"
160 echoerr
" -t, --temp-dir [path dir]"
161 echoerr
" Specify parent directory for temporary working directory."
162 echoerr
" Default: \"/dev/shm\""
163 echoerr
" -R, --recipient-dir [path dir]"
164 echoerr
" Specify directory containing files whose first lines are"
165 echoerr
" to be interpreted as pubkey strings (see \\'-r\\' option)."
166 echoerr
" -b, --buffer-ttl [integer]"
167 echoerr
" Specify custom buffer period in seconds (default: 300 seconds)"
168 echoerr
" -B, --script-ttl [integer]"
169 echoerr
" Specify custom script time-to-live in seconds (default: \"day\")"
171 echoerr
"EXAMPLE: (bash script lines)"
172 echoerr
"/bin/bash bkgpslog -v -e -c \\"
173 echoerr
"-z \"UTC\" -t \"/dev/shm\" \\"
174 echoerr
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
175 echoerr
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
176 echoerr
"-o ~/Sync/Location"
177 } # Display information on how to use this script.
179 echoerr
"$scriptVersion"
180 } # Display script version.
182 # Description: Prints verbose message ("vbm") to stderr if optionVerbose is set to "true".
183 # Usage: vbm "DEBUG:verbose message here"
185 # Input: arg1: string
186 # vars: optionVerbose
188 # Depends: echo 8, date 8
190 if [ "$optionVerbose" = "true" ]; then
191 functionTime
=$
(date --iso-8601=ns
); # Save current time in nano seconds.
192 echo "[$functionTime] ""$*" 1>&2; # Display argument text.
196 return 0; # Function finished.
197 } # Displays message if optionVerbose true
199 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
200 #echoerr "DEBUG:Starting processArguments while loop."
201 #echoerr "DEBUG:Provided arguments are:""$*"
203 -h |
--help) showUsage
; exit 1;; # Display usage.
204 --version) showVersion
; exit 1;; # Show version
205 -v |
--verbose) optionVerbose
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode.
206 -o |
--output) if [ -d "$2" ]; then dirOut
="$2"; vbm
"DEBUG:dirOut:$dirOut"; shift; fi ;; # Define output directory.
207 -e |
--encrypt) optionEncrypt
="true"; vbm
"DEBUG:Encrypted output mode enabled.";; # Enable encryption
208 -r |
--recipient) optionRecipients
="true"; argRecPubKeys
+=("$2"); vbm
"STATUS:pubkey added:""$2"; shift;; # Add recipients
209 -c |
--compress) optionCompress
="true"; vbm
"DEBUG:Compressed output mode enabled.";; # Enable compression
210 -z |
--time-zone) try setTimeZoneEV
"$2"; shift;; # Set timestamp timezone
211 -t |
--temp-dir) optionTmpDir
="true" && argTempDirPriority
="$2"; shift;; # Set time zone
212 -R |
--recipient-dir) optionRecipients
="true"; optionRecDir
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir
213 -b |
--buffer-ttl) optionCustomBufferTTL
="true" && argCustomBufferTTL
="$2"; shift;; # Set custom buffer period (default: 300 seconds)
214 -B |
--script-ttl) optionCustomScriptTTL_TE
="true" && argCustomScriptTTL
="$2"; shift;; # Set custom script TTL (default: "day")
215 *) echoerr
"ERROR: Unrecognized argument: $1"; echoerr
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
219 } # Argument Processing
221 # Desc: Set time zone environment variable TZ
222 # Usage: setTimeZoneEV arg1
224 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
225 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
227 # exit code 0 on success
228 # exit code 1 on incorrect number of arguments
229 # exit code 2 if unable to validate arg1
230 # Depends: yell, printenv, bash 5
231 # Tested on: Debian 10
232 local tzDir returnState argTimeZone
235 if ! [[ $# -eq 1 ]]; then
236 yell
"ERROR:Invalid argument count.";
240 # Read TZDIR env var if available
241 if printenv TZDIR
1>/dev
/null
2>&1; then
242 tzDir
="$(printenv TZDIR)";
244 tzDir
="/usr/share/zoneinfo";
248 if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then
249 yell
"ERROR:Invalid time zone argument.";
252 # Export ARG1 as TZ environment variable
253 TZ
="$argTimeZone" && export TZ
&& returnState
="true";
256 # Determine function return code
257 if [ "$returnState" = "true" ]; then
260 } # Exports TZ environment variable
262 # Desc: Report seconds until next day.
264 # Output: stdout: integer seconds until next day
265 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
266 # Usage: timeUntilNextDay
267 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
268 # Depends: date 8, echo 8, yell, try
270 local returnState timeCurrent timeNextDay secondsUntilNextDay returnState
271 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
272 timeNextDay
="$(date -d "$timeCurrent next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
273 secondsUntilNextDay
="$(( $(date +%s -d "$timeNextDay") - $(date +%s -d "$timeCurrent") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
274 if [[ "$secondsUntilNextDay" -gt 0 ]]; then
276 elif [[ "$secondsUntilNextDay" -eq 0 ]]; then
277 returnState
="warning_zero";
278 yell
"WARNING:Reported time until next day exactly zero.";
279 elif [[ "$secondsUntilNextDay" -lt 0 ]]; then
280 returnState
="warning_negative";
281 yell
"WARNING:Reported time until next day is negative.";
284 try
echo "$secondsUntilNextDay"; # Report
286 # Determine function return code
287 if [[ "$returnState" = "true" ]]; then
289 elif [[ "$returnState" = "warning_zero" ]]; then
291 elif [[ "$returnState" = "warning_negative" ]]; then
294 } # Report seconds until next day
296 # Desc: Report seconds until next hour
298 # Output: stdout: integer seconds until next hour
299 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
300 # Usage: timeUntilNextHour
301 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
303 local returnState timeCurrent timeNextHour secondsUntilNextHour
304 timeCurrent
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
305 timeNextHour
="$(date -d "$timeCurrent next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
306 secondsUntilNextHour
="$(( $(date +%s -d "$timeNextHour") - $(date +%s -d "$timeCurrent") ))"; # Calculate seconds until next hour (res. 1 second).
307 if [[ "$secondsUntilNextHour" -gt 0 ]]; then
309 elif [[ "$secondsUntilNextHour" -eq 0 ]]; then
310 returnState
="warning_zero";
311 yell
"WARNING:Reported time until next hour exactly zero.";
312 elif [[ "$secondsUntilNextHour" -lt 0 ]]; then
313 returnState
="warning_negative";
314 yell
"WARNING:Reported time until next hour is negative.";
317 try
echo "$secondsUntilNextHour"; # Report
319 # Determine function return code
320 if [[ "$returnState" = "true" ]]; then
322 elif [[ "$returnState" = "warning_zero" ]]; then
324 elif [[ "$returnState" = "warning_negative" ]]; then
327 } # Report seconds until next hour
329 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
330 # Usage: dateTimeShort ([str date])
332 # Input: arg1: 'date'-parsable timestamp string (optional)
333 # Output: stdout: timestamp (ISO-8601, no separators)
335 local argTime timeCurrent timeInput timeCurrentShort
339 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
340 # Decide to parse current or supplied date
341 ## Check if time argument empty
342 if [[ -z "$argTime" ]]; then
343 ## T: Time argument empty, use current time
344 timeInput
="$timeCurrent";
346 ## F: Time argument exists, validate time
347 if date --date="$argTime" 1>/dev
/null
2>&1; then
348 ### T: Time argument is valid; use it
349 timeInput
="$argTime";
351 ### F: Time argument not valid; exit
352 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
355 # Construct and deliver separator-les date string
356 timeCurrentShort
="$(date -d "$timeInput" +%Y%m%dT%H%M%S%z)";
357 echo "$timeCurrentShort";
358 } # Get YYYYmmddTHHMMSS±zzzz
360 # Desc: Date without separators (YYYYmmdd)
361 # Usage: dateShort ([str date])
363 # Input: arg1: 'date'-parsable timestamp string (optional)
364 # Output: stdout: date (ISO-8601, no separators)
366 local argTime timeCurrent timeInput dateCurrentShort
370 timeCurrent
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
371 # Decide to parse current or supplied date
372 ## Check if time argument empty
373 if [[ -z "$argTime" ]]; then
374 ## T: Time argument empty, use current time
375 timeInput
="$timeCurrent";
377 ## F: Time argument exists, validate time
378 if date --date="$argTime" 1>/dev
/null
2>&1; then
379 ### T: Time argument is valid; use it
380 timeInput
="$argTime";
382 ### F: Time argument not valid; exit
383 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
386 # Construct and deliver separator-les date string
387 dateCurrentShort
="$(date -d "$timeInput" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
388 echo "$dateCurrentShort";
391 # Desc: Given seconds, output ISO-8601 duration string
392 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
393 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
394 # Usage: timeDuration [1:seconds] ([2:precision])
396 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
397 # arg2: precision level (optional; default=2)
398 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
399 # exit code 0: success
400 # exit code 1: error_input
401 # exit code 2: error_unknown
402 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
403 # Depends: date 8, bash 5, yell,
404 local argSeconds argPrecision precision returnState remainder
405 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
406 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
407 local witherPrecision output
408 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
410 argSeconds
="$1"; # read arg1 (seconds)
411 argPrecision
="$2"; # read arg2 (precision)
412 precision
=2; # set default precision
414 # Check that between one and two arguments is supplied
415 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
416 yell
"ERROR:Invalid number of arguments:$# . Exiting.";
417 returnState
="error_input"; fi
419 # Check that argSeconds provided
420 if [[ $# -ge 1 ]]; then
421 ## Check that argSeconds is a positive integer
422 if [[ "$argSeconds" =~ ^
[[:digit
:]]+$
]]; then
425 yell
"ERROR:argSeconds not a digit.";
426 returnState
="error_input";
429 yell
"ERROR:No argument provided. Exiting.";
433 # Consider whether argPrecision was provided
434 if [[ $# -eq 2 ]]; then
435 # Check that argPrecision is a positive integer
436 if [[ "$argPrecision" =~ ^
[[:digit
:]]+$
]] && [[ "$argPrecision" -gt 0 ]]; then
437 precision
="$argPrecision";
439 yell
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
440 returnState
="error_input";
446 remainder
="$argSeconds" ; # seconds
447 ## Calculate full years Y, update remainder
448 fullYears
=$
(( remainder
/ (365*24*60*60) ));
449 remainder
=$
(( remainder
- (fullYears
*365*24*60*60) ));
450 ## Calculate full months M, update remainder
451 fullMonths
=$
(( remainder
/ (30*24*60*60) ));
452 remainder
=$
(( remainder
- (fullMonths
*30*24*60*60) ));
453 ## Calculate full days D, update remainder
454 fullDays
=$
(( remainder
/ (24*60*60) ));
455 remainder
=$
(( remainder
- (fullDays
*24*60*60) ));
456 ## Calculate full hours H, update remainder
457 fullHours
=$
(( remainder
/ (60*60) ));
458 remainder
=$
(( remainder
- (fullHours
*60*60) ));
459 ## Calculate full minutes M, update remainder
460 fullMinutes
=$
(( remainder
/ (60) ));
461 remainder
=$
(( remainder
- (fullMinutes
*60) ));
462 ## Calculate full seconds S, update remainder
463 fullSeconds
=$
(( remainder
/ (1) ));
464 remainder
=$
(( remainder
- (remainder
*1) ));
465 ## Check which fields filled
466 if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi
467 if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi
468 if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi
469 if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi
470 if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi
471 if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi
473 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
474 witherPrecision
="false"
477 if $hasYears && [[ $precision -gt 0 ]]; then
479 witherPrecision
="true";
481 displayYears
="false";
483 if $witherPrecision; then ((precision--
)); fi;
486 if $hasMonths && [[ $precision -gt 0 ]]; then
487 displayMonths
="true";
488 witherPrecision
="true";
490 displayMonths
="false";
492 if $witherPrecision && [[ $precision -gt 0 ]]; then
493 displayMonths
="true";
495 if $witherPrecision; then ((precision--
)); fi;
498 if $hasDays && [[ $precision -gt 0 ]]; then
500 witherPrecision
="true";
504 if $witherPrecision && [[ $precision -gt 0 ]]; then
507 if $witherPrecision; then ((precision--
)); fi;
510 if $hasHours && [[ $precision -gt 0 ]]; then
512 witherPrecision
="true";
514 displayHours
="false";
516 if $witherPrecision && [[ $precision -gt 0 ]]; then
519 if $witherPrecision; then ((precision--
)); fi;
522 if $hasMinutes && [[ $precision -gt 0 ]]; then
523 displayMinutes
="true";
524 witherPrecision
="true";
526 displayMinutes
="false";
528 if $witherPrecision && [[ $precision -gt 0 ]]; then
529 displayMinutes
="true";
531 if $witherPrecision; then ((precision--
)); fi;
535 if $hasSeconds && [[ $precision -gt 0 ]]; then
536 displaySeconds
="true";
537 witherPrecision
="true";
539 displaySeconds
="false";
541 if $witherPrecision && [[ $precision -gt 0 ]]; then
542 displaySeconds
="true";
544 if $witherPrecision; then ((precision--
)); fi;
546 ## Determine whether or not the "T" separator is needed to separate date and time elements
547 if ( $displayHours ||
$displayMinutes ||
$displaySeconds); then
548 displayDateTime
="true"; else displayDateTime
="false"; fi
550 ## Construct duration output string
552 if $displayYears; then
553 output
=$output$fullYears"Y"; fi
554 if $displayMonths; then
555 output
=$output$fullMonths"M"; fi
556 if $displayDays; then
557 output
=$output$fullDays"D"; fi
558 if $displayDateTime; then
559 output
=$output"T"; fi
560 if $displayHours; then
561 output
=$output$fullHours"H"; fi
562 if $displayMinutes; then
563 output
=$output$fullMinutes"M"; fi
564 if $displaySeconds; then
565 output
=$output$fullSeconds"S"; fi
567 ## Output duration string to stdout
568 echo "$output" && returnState
="true";
570 #===Determine function return code===
571 if [ "$returnState" = "true" ]; then
573 elif [ "$returnState" = "error_input" ]; then
577 yell
"ERROR:Unknown";
581 } # Get duration (ex: PT10M4S )
583 # Desc: Displays missing apps, files, and dirs
584 # Usage: displayMissing
586 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
587 # Output: stderr: messages indicating missing apps, file, or dirs
588 # Depends: bash 5, checkAppFileDir()
589 #==BEGIN Display errors==
590 #===BEGIN Display Missing Apps===
591 missingApps
="Missing apps :"
592 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
593 for key
in "${!appRollCall[@]}"; do
594 value
="${appRollCall[$key]}"
595 if [ "$value" = "false" ]; then
596 #echo "DEBUG:Missing apps: $key => $value";
597 missingApps
="$missingApps""$key "
601 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
602 echo "$missingApps" 1>&2;
604 #===END Display Missing Apps===
606 #===BEGIN Display Missing Files===
607 missingFiles
="Missing files:"
608 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
609 for key
in "${!fileRollCall[@]}"; do
610 value
="${fileRollCall[$key]}"
611 if [ "$value" = "false" ]; then
612 #echo "DEBUG:Missing files: $key => $value";
613 missingFiles
="$missingFiles""$key "
617 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
618 echo "$missingFiles" 1>&2;
620 #===END Display Missing Files===
622 #===BEGIN Display Missing Directories===
623 missingDirs
="Missing dirs:"
624 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
625 for key
in "${!dirRollCall[@]}"; do
626 value
="${dirRollCall[$key]}"
627 if [ "$value" = "false" ]; then
628 #echo "DEBUG:Missing dirs: $key => $value";
629 missingDirs
="$missingDirs""$key "
633 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
634 echo "$missingDirs" 1>&2;
636 #===END Display Missing Directories===
638 #==END Display errors==
639 } # Display missing apps, files, dirs
640 magicSetScriptTTL
() {
641 #Desc: Sets script_TTL seconds from provided time_element string argument
642 #Usage: magicSetScriptTTL [str time_element]
643 #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour")
644 #Output: var: scriptTTL (integer seconds)
645 #Depends: timeUntilNextHour, timeUntilNextDay
648 if [[ "$argTimeElement" = "day" ]]; then
649 # Set script lifespan to end at start of next day
650 if ! scriptTTL
="$(timeUntilNextDay)"; then
651 if [[ "$scriptTTL" -eq 0 ]]; then
652 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
654 yell
"ERROR: timeUntilNextDay exit code $?"; exit 1;
657 elif [[ "$argTimeElement" = "hour" ]]; then
658 # Set script lifespan to end at start of next hour
659 if ! scriptTTL
="$(timeUntilNextHour)"; then
660 if [[ "$scriptTTL" -eq 0 ]]; then
661 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
663 yell
"ERROR: timeUntilNextHour exit code $?"; exit 1;
667 yell
"ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
669 } # Seconds until next (day|hour).
671 # Desc: Checks that a valid tar archive exists, creates one otherwise
672 # Usage: checkMakeTar [ path ]
674 # Input: arg1: path of tar archive
675 # Output: exit code 0 : tar readable
676 # exit code 1 : tar missing; created
677 # exit code 2 : tar not readable; moved; replaced
678 # Depends: bash 5, date 8, tar 1, try()
679 local pathTar returnFlag0 returnFlag1 returnFlag2
682 # Check if file is a valid tar archive
683 if tar --list --file="$pathTar" 1>/dev
/null
2>&1; then
684 ## T1: return success
685 returnFlag0
="tar valid";
687 ## F1: Check if file exists
688 if [[ -f "$pathTar" ]]; then
690 try
mv "$pathTar" "$pathTar""--broken--""$(date +%Y%m%dT%H%M%S)" && \
691 returnFlag1
="tar moved";
696 ## F2: Create tar archive, return 0
697 try
tar --create --file="$pathTar" --files-from=/dev
/null
&& \
698 returnFlag2
="tar created";
701 # Determine function return code
702 if [[ "$returnFlag0" = "tar valid" ]]; then
704 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
705 return 1; # tar missing so created
706 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
707 return 2; # tar not readable so moved; replaced
709 } # checks if arg1 is tar; creates one otherwise
711 # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar
712 # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
714 # Input: arg1: data to be written
715 # arg2: file name of file to be inserted into tar
716 # arg3: tar archive path (must exist first)
717 # arg4: temporary working dir
718 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
719 # Output: file written to disk
720 # Example: decrypt multiple large files in parallel
721 # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
722 # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
723 # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
724 # Depends: bash 5, tar 1, yell()
725 # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533
727 local fn fileName tarPath tmpDir cmd0 cmd1 cmd2 cmd3 cmd4
731 #yell "DEBUG:STATUS:$fn:Finished appendArgTar()."
734 if ! [ -z "$2" ]; then fileName
="$2"; else yell
"ERROR:$fn:Not enough arguments."; exit 1; fi
736 # Check tar path is a file
737 if [ -f "$3" ]; then tarPath
="$3"; else yell
"ERROR:$fn:Tar archive arg not a file."; exit 1; fi
740 if ! [ -z "$4" ]; then tmpDir
="$4"; else yell
"ERROR:$fn:No temporary working dir set."; exit 1; fi
742 # Set command strings
743 if ! [ -z "$5" ]; then cmd1
="$5"; else cmd1
="cat "; fi # command string 1
744 if ! [ -z "$6" ]; then cmd2
="$6"; else cmd2
="cat "; fi # command string 2
745 if ! [ -z "$7" ]; then cmd3
="$7"; else cmd3
="cat "; fi # command string 3
746 if ! [ -z "$8" ]; then cmd4
="$8"; else cmd4
="cat "; fi # command string 4
752 # yell "DEBUG:STATUS:$fn:cmd0:$cmd0"
753 # yell "DEBUG:STATUS:$fn:cmd1:$cmd1"
754 # yell "DEBUG:STATUS:$fn:cmd2:$cmd2"
755 # yell "DEBUG:STATUS:$fn:cmd3:$cmd3"
756 # yell "DEBUG:STATUS:$fn:cmd4:$cmd4"
757 # yell "DEBUG:STATUS:$fn:fileName:$fileName"
758 # yell "DEBUG:STATUS:$fn:tarPath:$tarPath"
759 # yell "DEBUG:STATUS:$fn:tmpDir:$tmpDir"
761 # Write to temporary working dir
762 eval "$cmd0 | $cmd1 | $cmd2 | $cmd3 | $cmd4" > "$tmpDir"/"$fileName";
765 try
tar --append --directory="$tmpDir" --file="$tarPath" "$fileName";
766 #yell "DEBUG:STATUS:$fn:Finished appendArgTar()."
767 } # Append Bash var to file appended to Tar archive
769 # Desc: Processes first file and then appends to tar
770 # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
772 # Input: arg1: path of file to be (processed and) written
773 # arg2: name to use for file inserted into tar
774 # arg3: tar archive path (must exist first)
775 # arg4: temporary working dir
776 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
777 # Output: file written to disk
778 # Example: decrypt multiple large files in parallel
779 # appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
780 # appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
781 # appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
782 # Depends: bash 5, tar 1, yell()
784 local fn fileName tarPath tmpDir
788 #yell "DEBUG:STATUS:$fn:Finished appendFileTar()."
791 if ! [ -z "$2" ]; then fileName
="$2"; else yell
"ERROR:$fn:Not enough arguments."; exit 1; fi
792 # Check tar path is a file
793 if [ -f "$3" ]; then tarPath
="$3"; else yell
"ERROR:$fn:Tar archive arg not a file:$3"; exit 1; fi
795 if ! [ -z "$4" ]; then tmpDir
="$4"; else yell
"ERROR:$fn:No temporary working dir set."; exit 1; fi
796 # Set command strings
797 if ! [ -z "$5" ]; then cmd1
="$5"; else cmd1
="cat "; fi # command string 1
798 if ! [ -z "$6" ]; then cmd2
="$6"; else cmd2
="cat "; fi # command string 2
799 if ! [ -z "$7" ]; then cmd3
="$7"; else cmd3
="cat "; fi # command string 3
800 if ! [ -z "$8" ]; then cmd4
="$8"; else cmd4
="cat "; fi # command string 4
802 # Input command string
806 # yell "DEBUG:STATUS:$fn:cmd0:$cmd0"
807 # yell "DEBUG:STATUS:$fn:cmd1:$cmd1"
808 # yell "DEBUG:STATUS:$fn:cmd2:$cmd2"
809 # yell "DEBUG:STATUS:$fn:cmd3:$cmd3"
810 # yell "DEBUG:STATUS:$fn:cmd4:$cmd4"
811 # yell "DEBUG:STATUS:$fn:fileName:$fileName"
812 # yell "DEBUG:STATUS:$fn:tarPath:$tarPath"
813 # yell "DEBUG:STATUS:$fn:tmpDir:$tmpDir"
815 # Write to temporary working dir
816 eval "$cmd0 | $cmd1 | $cmd2 | $cmd3 | $cmd4" > "$tmpDir"/"$fileName";
819 try
tar --append --directory="$tmpDir" --file="$tarPath" "$fileName";
820 #yell "DEBUG:STATUS:$fn:Finished appendFileTar()."
821 } # Append file to Tar archive
823 # Desc: Checks if string is an age-compatible pubkey
824 # Usage: checkAgePubkey [str pubkey]
826 # Input: arg1: string
827 # Output: return code 0: string is age-compatible pubkey
828 # return code 1: string is NOT an age-compatible pubkey
829 # age stderr (ex: there is stderr if invalid string provided)
830 # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 )
834 if echo "test" | age
-a -r "$argPubkey" 1>/dev
/null
; then
841 # Desc: Validates Input
842 # Usage: validateInput [str input] [str input type]
844 # Input: arg1: string to validate
845 # arg2: string specifying input type (ex:"ssh_pubkey")
846 # Output: return code 0: if input string matched specified string type
847 # Depends: bash 5, yell()
849 local fn argInput argType
857 if [[ $# -gt 2 ]]; then yell
"ERROR:$0:$fn:Too many arguments."; exit 1; fi;
860 if [[ -z "$argInput" ]]; then return 1; fi
864 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
865 if [[ "$argType" = "ssh_pubkey" ]]; then
866 if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\
]*[[:alnum
:]+/=]*$
]]; then
870 ### Check for age1[:bech32:]
871 if [[ "$argType" = "age_pubkey" ]]; then
872 if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$
]]; then
876 if [[ "$argType" = "integer" ]]; then
877 if [[ "$argInput" =~ ^
[[:digit
:]]*$
]]; then
880 ## time element (year, month, week, day, hour, minute, second)
881 if [[ "$argType" = "time_element" ]]; then
882 if [[ "$argInput" = "year" ]] || \
883 [[ "$argInput" = "month" ]] || \
884 [[ "$argInput" = "week" ]] || \
885 [[ "$argInput" = "day" ]] || \
886 [[ "$argInput" = "hour" ]] || \
887 [[ "$argInput" = "minute" ]] || \
888 [[ "$argInput" = "second" ]]; then
891 # Return error if no condition matched.
893 } # Validates strings
895 # Desc: Get epoch nanoseconds
898 # Input: arg1: 'date'-parsable timestamp string (optional)
899 # Output: Nanoseconds since 1970-01-01
900 # Depends: date 8, cut 8, yell()
901 # Ref/Attrib: Force base 10 Bash arith with '10#'. https://stackoverflow.com/a/24777667
902 local argTime timeCurrent timeInput timeEpochFloat timeEpochInt
903 local timeEpochNsFrac timeEpochNs
908 timeCurrent
="$(date --iso-8601=ns)"; # Produce `date`-parsable current timestamp with resolution of 1 nanosecond.
910 # Decide to parse current or supplied time
911 ## Check if time argument empty
912 if [[ -z "$argTime" ]]; then
913 ## T: Time argument empty, use current time
914 timeInput
="$timeCurrent";
916 ## F: Time argument exists, validate time
917 if date --date="$argTime" 1>/dev
/null
2>&1; then
918 ### T: Time argument is valid; use it
919 timeInput
="$argTime";
921 ### F: Time argument not valid; exit
922 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
925 # Construct and deliver nanoseconds since 1970-01-01
926 timeEpochFloat
="$(date --date="$timeInput" +%s.%N)"; # Save ssss.NNNNNNNNN
927 timeEpochInt
="$(echo "$timeEpochFloat" | cut -d. -f1)"; # Get ssss
928 timeEpochNsFrac
="$(echo "$timeEpochFloat" | cut -d. -f2)"; # Get NNNNNNNNN
929 timeEpochNs
="$(( (10#"$timeEpochInt" * 10**9) + (10#"$timeEpochNsFrac") ))";
931 } # Nanoseconds since 1970-01-01
932 magicBufferSleepPID
() {
933 # Desc: Compensates for lag so buffer rounds start every bufferTTL seconds
934 # Input: vars: bufferTTL, errReset
935 # # Input: array: errorHistory errResetx10e3
936 # Output: vars: bufferTTL_AdjFloat
937 # Re/Attrib: https://en.wikipedia.org/wiki/PID_controller#Standard_versus_parallel_(ideal)_form
938 local buffer_ttl_ns k_p t_i t_d
939 local timeBufferStartNS timeBufferStartNSExp errNS errReset errRate
940 local adj buffer_ttl_adj_ns buffer_ttl_adj_int buffer_ttl_adj_floatfrac
941 # local errNSx10e3 errResetx10e3 errRatex10e3
942 # local errorHistorySize
944 # ## Define errorHistorySize
945 # errorHistorySize=100;
946 ## Define bufferTTL in nanoseconds
947 buffer_ttl_ns
=$
((bufferTTL
* 10**9)) && vbm
"buffer_ttl_ns:$buffer_ttl_ns";
949 ### PID Control factors
950 k_p
=1; # Gain for compensating buffer round lag
951 t_i
="$(((4)*buffer_ttl_ns/(1)))"; # Consider this number of past nanoseconds to eliminate error
952 t_d
="$(((1)*buffer_ttl_ns/(1)))"; # Predict value this number of nanoseconds into the future
954 # Calculate Error, errNS, in nanoseconds
956 timeBufferStartNS
="$(timeEpochNS)" && vbm
"timeBufferStartNS :$timeBufferStartNS";
957 ## Calculate expected time (from start time, current buffer round number, nominal bufferTTL)
958 timeBufferStartNSExp
="$(( (timeBufferFirstNS) + (buffer_ttl_ns * bufferRound) ))" && vbm
"timeBufferStartNSExp:$timeBufferStartNSExp";
959 ## Calculate error (diff between timeBufferStartNSExp and timeBufferStartNS; usually negative)
960 errNS
="$(( timeBufferStartNSExp - timeBufferStartNS ))" && vbm
"errNS:$errNS";
961 # errNSx10e3="$((errNS*10**3))" && vbm "errNSx10e3:$errNSx10e3";
962 # ## Append error to errorHistory
963 # errorHistory+=("errNS");
964 # ### Trim errorHistory array if over errorHistorySize
965 # while [[ "${#errorHistory[@]}" -gt "errorHistorySize" ]]; then do
966 # unset "errorHistory[0]"; # remove oldest entry, creating sparse array
967 # errorHistory=("${errorHistory[@]}"); # reindex sparse array
968 # vbm "STATUS:Trimmed errorHistory array. Entry count:${#errorHistory[@]}";
971 # Calculate errReset in nanoseconds^2
972 ## errReset = int(errHistory(t),wrt(delta_buffer_ttl))
973 ## Integrate errorHistory with respect to time
974 # for value in "${errorHistory[@]}"; do
975 # errReset=$(( errReset + ( value*buffer_ttl_ns ) ));
977 vbm
"errReset(orig):$errReset"
978 errReset
="$(( (errReset + (errNS*buffer_ttl_ns)) ))" && vbm
"errReset(post):$errReset";
979 # errResetx10e3="$(( ( errResetx10e3 + ( errNSx10e3 * buffer_ttl_ns ) )*10**3 ))" && vbm "errResetx10e3:$errResetx10e3";
981 # Calculate errRate in nanoseconds per nanosecond
982 errRate
="$(( errNS / buffer_ttl_ns ))" && vbm
"errRate:$errRate";
983 # errRatex10e3="$(( ( errNSx10e3 ) / buffer_ttl_ns ))" && vbm "errRatex10e3:$errRatex10e3";
987 vbm
"errResetTerm:$((errReset/t_i))";
988 vbm
"errRateTerm :$((errRate*t_d))";
990 # Calculate PID control signal
991 ## adj = k_p * (errNS + errReset/t_i + errRate*t_d)
992 adj
="$(( k_p*(errNS + errReset/t_i + errRate*t_d) ))" && vbm
"adj:$adj";
993 # adj="$((k_p*(errNSx10e3 + (errResetx10e3/t_i) + (errRatex10e3*t_d) )/(10**3)))" && vbm "adj:$adj";
995 # Calculate bufferTTL_AdjFloat from adj (ns)
996 ## Calculate buffer_ttl_adj in nanoseconds (buffer_ttl_adj_ns = buffer_ttl_ns + adj)
997 buffer_ttl_adj_ns
="$((buffer_ttl_ns + adj))" && vbm
"buffer_ttl_adj_ns:$buffer_ttl_adj_ns";
998 ## Calculate integer seconds
999 buffer_ttl_adj_int
="$((buffer_ttl_adj_ns/(10**9)))" && vbm
"buffer_ttl_adj_int:$buffer_ttl_adj_int";
1000 ### Catch negative integer seconds, set minimum of bufferTTL/10 seconds
1001 if [[ "$buffer_ttl_adj_int" -le "$((bufferTTL/10))" ]]; then
1002 buffer_ttl_adj_int
="$((bufferTTL/10))";
1003 yell
"WARNING:Buffer lag adjustment yielded negative seconds.";
1005 ## Calculate nanosecond remainder
1007 buffer_ttl_adj_floatfrac
="$((buffer_ttl_adj_ns - (buffer_ttl_adj_int*(10**9)) ))" && vbm
"buffer_ttl_adj_floatfrac:$buffer_ttl_adj_floatfrac";
1008 ### Calc absolute value of fraction (by removing '-' if present; see https://stackoverflow.com/a/47240327
1009 buffer_ttl_adj_floatfrac
="${buffer_ttl_adj_floatfrac#-}" && vbm
"buffer_ttl_adj_floatfrac:$buffer_ttl_adj_floatfrac";
1010 ## Form float bufferTTL_AdjFloat (function output)
1011 bufferTTL_AdjFloat
="$buffer_ttl_adj_int".
"$buffer_ttl_adj_floatfrac" && vbm
"bufferTTL_AdjFloat:$bufferTTL_AdjFloat";
1012 vbm
"STATUS:Calculated adjusted bufferTTL (seconds):$bufferTTL_AdjFloat";
1013 } # Calc bufferTTL_AdjFloat so buffer starts every bufferTTL seconds
1014 magicWriteVersion
() {
1015 # Desc: Appends time-stamped VERSION to pathout_tar
1016 # Usage: magicWriteVersion
1017 # Input: vars: pathout_tar, dir_tmp
1018 # Input: array: recPubKeysValid
1019 # Input: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname
1020 # Output: appends tar (pathout_tar)
1021 # Depends: dateTimeShort, appendArgTar
1022 local fileoutVersion contentVersion pubKeyIndex
1024 # Set VERSION file name
1025 fileoutVersion
="$(dateTimeShort)..VERSION";
1027 # Gather VERSION data in contentVersion
1028 contentVersion
="scriptVersion=$scriptVersion";
1029 #contentVersion="$contentVersion""\\n";
1030 contentVersion
="$contentVersion""\\n""scriptName=$scriptName";
1031 contentVersion
="$contentVersion""\\n""scriptURL=$scriptURL";
1032 contentVersion
="$contentVersion""\\n""ageVersion=$ageVersion";
1033 contentVersion
="$contentVersion""\\n""ageURL=$ageURL";
1034 contentVersion
="$contentVersion""\\n""date=$(date --iso-8601=seconds)";
1035 contentVersion
="$contentVersion""\\n""hostname=$scriptHostname";
1036 ## Add list of recipient pubkeys
1037 for pubkey
in "${recPubKeysValid[@]}"; do
1039 contentVersion
="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1041 ## Process newline escapes
1042 contentVersion
="$(echo -e "$contentVersion")"
1044 # Write contentVersion as file fileoutVersion and write-append to pathout_tar
1045 appendArgTar
"$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp";
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, cmd_conv_{nmea,gpx,kml}, cmd_{compress,encrypt} dir_tmp,
1050 # Inputs: vars: bufferTTL bufferTTL_STR scriptHostname cmd_compress_suffix cmd_encrypt_suffix
1051 # Output: file: (pathout_tar)
1052 # Depends: tar 1, date 8, sleep 8, yell(), try(), vbm(), appendArgTar(), checkMakeTar()
1053 # Depends: magicWriteVersion(), appendFileTar()
1054 local fn pathoutBuffer timeBufferStartLong timeBufferStart fileoutBasename
1055 local fileout_nmea fileout_gpx fileout_kml pathout_nmea pathout_gpx pathout_kml pathout_tar
1057 # Debug:Get function name
1058 fn
="${FUNCNAME[0]}";
1060 vbm
"DEBUG:STATUS:$fn:Started magicGatherWriteBuffer().";
1062 # Create buffer file with unique name
1063 pathoutBuffer
="$dir_tmp/buffer$SECONDS" && vbm
"pathoutBuffer:$pathoutBuffer";
1065 timeout
"$bufferTTL"s gpspipe
-r -o "$pathoutBuffer" ;
1066 timeBufferStartLong
="$(date --date="$bufferTTL seconds ago
" --iso-8601=seconds)" && vbm
"timeBufferStartLong:$timeBufferStartLong";
1067 timeBufferStart
="$(dateTimeShort "$timeBufferStartLong" )" && vbm
"timeBufferStart:$timeBufferStart"; # Note start time
1068 # Determine file paths (time is start of buffer period)
1069 fileoutBasename
="$timeBufferStart""--""$bufferTTL_STR""..""$scriptHostname""_location" && vbm
"STATUS:Set fileoutBasename to:$fileoutBasename";
1070 ## Files saved to dir_tmp
1071 fileout_nmea
="$fileoutBasename".nmea
"$cmd_compress_suffix""$cmd_encrypt_suffix" && vbm
"STATUS:Set fileout_nmea to:$fileout_nmea";
1072 fileout_gpx
="$fileoutBasename".gpx
"$cmd_compress_suffix""$cmd_encrypt_suffix" && vbm
"STATUS:Set fileout_gpx to:$fileout_gpx";
1073 fileout_kml
="$fileoutBasename".kml
"$cmd_compress_suffix""$cmd_encrypt_suffix" && vbm
"STATUS:Set fileout_kml to:$fileout_kml";
1074 pathout_nmea
="$dir_tmp"/"$fileout_nmea" && vbm
"STATUS:Set pathout_nmea to:$pathout_nmea";
1075 pathout_gpx
="$dir_tmp"/"$fileout_gpx" && vbm
"STATUS:Set pathout_gpx to:$pathout_gpx";
1076 pathout_kml
="$dir_tmp"/"$fileout_kml" && vbm
"STATUS:Set pathout_kml to:$pathout_kml";
1077 ## Files saved to disk (dirOut)
1078 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
1079 pathout_tar
="$dirOut"/"$(dateShort "$
(date --date="$bufferTTL seconds ago" --iso-8601=seconds
)")"..
"$scriptHostname""_location""$cmd_compress_suffix""$cmd_encrypt_suffix".
tar && \
1080 vbm
"STATUS:Set pathout_tar to:$pathout_tar";
1082 vbm
"STATUS:fn :$fn";
1083 vbm
"STATUS:dir_tmp :$dir_tmp";
1084 vbm
"STATUS:pathout_tar :$pathout_tar";
1085 vbm
"STATUS:pathout_nmea :$pathout_nmea";
1086 vbm
"STATUS:pathout_gpx :$pathout_gpx";
1087 vbm
"STATUS:pathout_kml :$pathout_kml";
1088 vbm
"STATUS:bufferTTL :$bufferTTL";
1089 vbm
"STATUS:pathoutBuffer :$pathoutBuffer";
1090 vbm
"STATUS:timeBufferStart:$timeBufferStart";
1091 vbm
"fileoutBasename :$fileoutBasename";
1093 # Validate pathout_tar as tar.
1094 checkMakeTar
"$pathout_tar";
1095 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
1096 vbm
"exit status before magicWriteVersion:$?"
1097 if [[ $?
-eq 1 ]] ||
[[ $?
-eq 2 ]]; then magicWriteVersion
; fi
1099 # Write bufferBash to pathout_tar
1100 wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
1101 appendFileTar
"$pathoutBuffer" "$fileout_nmea" "$pathout_tar" "$dir_tmp" "$cmd_conv_nmea" "$cmd_compress" "$cmd_encrypt"; # Write NMEA data
1102 appendFileTar
"$pathoutBuffer" "$fileout_gpx" "$pathout_tar" "$dir_tmp" "$cmd_conv_gpx" "$cmd_compress" "$cmd_encrypt"; # Write GPX file
1103 appendFileTar
"$pathoutBuffer" "$fileout_kml" "$pathout_tar" "$dir_tmp" "$cmd_conv_kml" "$cmd_compress" "$cmd_encrypt"; # Write KML file
1105 # Remove secured chunks from dir_tmp
1106 rm "$pathoutBuffer" "$pathout_nmea" "$pathout_gpx" "$pathout_kml";
1107 vbm
"DEBUG:STATUS:$fn:Finished magicGatherWriteBuffer().";
1108 } # write buffer to disk
1109 magicParseRecipientDir
() {
1110 # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory")
1111 # Inputs: vars: optionRecDir, argRecDir, optionEncrypt
1112 # arry: recPubKeysValid
1113 # Outputs: arry: recPubKeysValid
1114 # Depends: processArguments,
1115 local recipientDir recFileLine updateRecipients
1116 declare -a candRecPubKeysValid
1118 # Check that '-e' and '-R' set
1119 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
1120 ### Check that argRecDir is a directory.
1121 if [[ -d "$argRecDir" ]]; then
1122 recipientDir
="$argRecDir" && vbm
"STATUS:Recipient watch directory detected:\"$recipientDir\"";
1123 #### Initialize variable indicating outcome of pubkey review
1124 unset updateRecipients
1125 #### Add existing recipients
1126 candRecPubKeysValid
=("${recPubKeysValidStatic[@]}");
1127 #### Parse files in recipientDir
1128 for file in "$recipientDir"/*; do
1129 ##### Read first line of each file
1130 recFileLine
="$(head -n1 "$file")" && vbm
"STATUS:Checking if pubkey:\"$recFileLine\"";
1131 ##### check if first line is a valid pubkey
1132 if checkAgePubkey
"$recFileLine" && \
1133 ( validateInput
"$recFileLine" "ssh_pubkey" || validateInput
"$recFileLine" "age_pubkey"); then
1134 ###### T: add candidate pubkey to candRecPubKeysValid
1135 candRecPubKeysValid
+=("$recFileLine") && vbm
"STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\"";
1137 ###### F: throw warning;
1138 yell
"ERROR:Invalid recipient file detected. Not modifying recipient list."
1139 updateRecipients
="false";
1142 #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected
1143 if ! [[ "$updateRecipients" = "false" ]]; then
1144 recPubKeysValid
=("${candRecPubKeysValid[@]}") && vbm
"STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\"";
1147 yell
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
1150 # Handle case if '-R' set but '-e' not set
1151 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
1152 yell
"ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi;
1153 } # Update recPubKeysValid with argRecDir
1154 magicParseRecipientArgs
() {
1155 # Desc: Parses recipient arguments specified by '-r' option
1156 # Input: vars: optionEncrypt, optionRecipients
1157 # arry: argRecPubKeys from processArguments()
1158 # Output: vars: cmd_encrypt, cmd_encrypt_suffix
1159 # arry: recPubKeysValid, recPubKeysValidStatic
1160 # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments()
1163 # Check if encryption option active.
1164 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
1165 if checkapp age
; then # Check that age is available.
1166 for pubkey
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
1167 vbm
"DEBUG:Testing pubkey string:$pubkey";
1168 if checkAgePubkey
"$pubkey" && \
1169 ( validateInput
"$pubkey" "ssh_pubkey" || validateInput
"$pubkey" "age_pubkey"); then
1170 #### Form age recipient string
1171 recipients
="$recipients""-r '$pubkey' ";
1172 vbm
"STATUS:Added pubkey for forming age recipient string:""$pubkey";
1173 vbm
"DEBUG:recipients:""$recipients";
1174 #### Add validated pubkey to recPubKeysValid array
1175 recPubKeysValid
+=("$pubkey") && vbm
"DEBUG:recPubkeysValid:pubkey added:$pubkey";
1177 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
1180 vbm
"DEBUG:Finished processing argRecPubKeys array";
1181 vbm
"STATUS:Array of validated pubkeys:${recPubKeysValid[*]}";
1182 recPubKeysValidStatic
=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function
1184 ## Form age command string
1185 cmd_encrypt
="age ""$recipients " && vbm
"cmd_encrypt:$cmd_encrypt";
1186 cmd_encrypt_suffix
=".age" && vbm
"cmd_encrypt_suffix:$cmd_encrypt_suffix";
1188 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
1191 cmd_encrypt
="tee /dev/null " && vbm
"cmd_encrypt:$cmd_encrypt";
1192 cmd_encrypt_suffix
="" && vbm
"cmd_encrypt_suffix:$cmd_encrypt_suffix";
1193 vbm
"DEBUG:Encryption not enabled."
1195 # Catch case if '-e' is set but '-r' or '-R' is not
1196 if [[ "$optionEncrypt" = "true" ]] && [[ ! "$optionRecipients" = "true" ]]; then
1197 yell
"ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
1198 # Catch case if '-r' or '-R' set but '-e' is not
1199 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
1200 yell
"ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
1201 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
1202 magicParseCompressionArg
() {
1203 # Desc: Parses compression arguments specified by '-c' option
1204 # Input: vars: optionCompress
1205 # Output: cmd_compress, cmd_compress_suffix
1206 # Depends: checkapp(), vbm(), gzip,
1207 if [[ "$optionCompress" = "true" ]]; then # Check if compression option active
1208 if checkapp
gzip; then # Check if gzip available
1209 cmd_compress
="gzip " && vbm
"cmd_compress:$cmd_compress";
1210 cmd_compress_suffix
=".gz" && vbm
"cmd_compress_suffix:$cmd_compress_suffix";
1212 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
1215 cmd_compress
="tee /dev/null " && vbm
"cmd_compress:$cmd_compress";
1216 cmd_compress_suffix
="" && vbm
"cmd_compress_suffix:$cmd_compress_suffix";
1217 vbm
"DEBUG:Compression not enabled.";
1219 } # Form compression cmd string and filename suffix
1220 magicInitWorkingDir
() {
1221 # Desc: Determine temporary working directory from defaults or user input
1222 # Usage: magicInitWorkingDir
1223 # Input: vars: optionTmpDir, argTempDirPriority, dirTmpDefault
1224 # Input: vars: scriptTimeStart
1225 # Output: vars: dir_tmp
1226 # Depends: processArguments(), vbm(), yell()
1227 # Parse '-t' option (user-specified temporary working dir)
1228 ## Set dir_tmp_parent to user-specified value if specified
1229 local dir_tmp_parent
1231 if [[ "$optionTmpDir" = "true" ]]; then
1232 if [[ -d "$argTempDirPriority" ]]; then
1233 dir_tmp_parent
="$argTempDirPriority";
1235 yell
"WARNING:Specified temporary working directory not valid:$argTempDirPriority";
1236 exit 1; # Exit since user requires a specific temp dir and it is not available.
1239 ## Set dir_tmp_parent to default or fallback otherwise
1240 if [[ -d "$dirTmpDefault" ]]; then
1241 dir_tmp_parent
="$dirTmpDefault";
1242 elif [[ -d /tmp
]]; then
1243 yell
"WARNING:$dirTmpDefault not available. Falling back to /tmp .";
1244 dir_tmp_parent
="/tmp";
1246 yell
"ERROR:No valid working directory available. Exiting.";
1250 ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart)
1251 dir_tmp
="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm
"DEBUG:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main().
1252 } # Sets working dir
1253 magicParseCustomTTL
() {
1254 # Desc: Set user-specified TTLs for buffer and script
1255 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
1256 # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE
1257 # Input: vars: bufferTTL (integer), scriptTTL_TE (string)
1258 # Output: bufferTTL (integer), scriptTTL_TE (string)
1259 # Depends validateInput(), showUsage(), yell
1261 # React to '-b, --buffer-ttl' option
1262 if [[ "$optionCustomBufferTTL" = "true" ]]; then
1263 ## T: Check if argCustomBufferTTL is an integer
1264 if validateInput
"$argCustomBufferTTL" "integer"; then
1265 ### T: argCustomBufferTTL is an integer
1266 bufferTTL
="$argCustomBufferTTL" && vbm
"Custom bufferTTL from -b:$bufferTTL";
1268 ### F: argcustomBufferTTL is not an integer
1269 yell
"ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1;
1271 ## F: do not change bufferTTL
1274 # React to '-B, --script-ttl' option
1275 if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then
1276 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
1277 if validateInput
"$argCustomScriptTTL" "time_element"; then
1278 ### T: argCustomScriptTTL is a time element
1279 scriptTTL_TE
="$argCustomScriptTTL" && vbm
"Custom scriptTTL_TE from -B:$scriptTTL_TE";
1281 ### F: argcustomScriptTTL is not a time element
1282 yell
"ERROR:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1;
1284 ## F: do not change scriptTTL_TE
1286 } # Sets custom script or buffer TTL if specified
1290 # DEBUG: Print environment variables
1291 vbm
"echo $(printenv)";
1293 processArguments
"$@";
1294 ## Act upon arguments
1295 ### Determine working directory
1296 magicInitWorkingDir
; # Sets dir_tmp from argTempDirPriority
1297 ### Set output encryption and compression option strings
1298 #### React to "-r" ("encryption recipients") option
1299 magicParseRecipientArgs
; # Updates recPubKeysValid, cmd_encrypt[_suffix] from argRecPubKeys
1300 #### React to "-c" ("compression") option
1301 magicParseCompressionArg
; # Updates cmd_compress[_suffix]
1302 #### React to "-R" ("recipient directory") option
1303 magicParseRecipientDir
; # Updates recPubKeysValid
1304 #### React to custom buffer and script TTL options ("-b", "-B")
1305 magicParseCustomTTL
; # Sets custom scriptTTL_TE and/or bufferTTL if specified
1307 # Check that critical apps and dirs are available, display missing ones.
1308 if ! checkapp gpspipe
tar && ! checkdir
"$dirOut" "dir_tmp"; then
1309 yell
"ERROR:Critical components missing.";
1310 displayMissing
; yell
"Exiting."; exit 1; fi
1312 # Set script lifespan (scriptTTL from scriptTTL_TE)
1313 magicSetScriptTTL
"$scriptTTL_TE";
1314 ## Note: scriptTTL_TE is time element string (ex: "day") while scriptTTL is integer seconds
1316 # File name substring (ISO-8601 duration from bufferTTL)
1317 bufferTTL_STR
="$(timeDuration "$bufferTTL")" && vbm
"DEBUG:bufferTTL_STR:$bufferTTL_STR";
1319 # Init temp working dir
1320 try mkdir
"$dir_tmp" && vbm
"DEBUG:Working dir created at dir_tmp:$dir_tmp";
1322 # Initialize 'tar' archive
1323 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
1324 pathout_tar
="$dirOut"/"$(dateShort "$
(date --date="$bufferTTL seconds ago" --iso-8601=seconds
)")"..
"$scriptHostname""_location""$cmd_compress_suffix""$cmd_encrypt_suffix".
tar && \
1325 vbm
"STATUS:Set pathout_tar to:$pathout_tar";
1326 ## Check that pathout_tar is a tar. Rename old and create empty one otherwise.
1327 checkMakeTar
"$pathout_tar" && vbm
"DEBUG:Confirmed or Created to be a tar:$pathout_tar";
1328 ## Append VERSION file to pathout_tar
1331 # Define GPS conversion commands
1332 cmd_conv_nmea
="tee /dev/null " && vbm
"STATUS:Set cmd_conv_nmea to:$cmd_conv_nmea"; # tee as passthrough
1333 cmd_conv_gpx
="gpsbabel -i nmea -f - -o gpx -F - " && vbm
"STATUS:Set cmd_conv_gpx to:$cmd_conv_gpx"; # convert NMEA to GPX
1334 cmd_conv_kml
="gpsbabel -i nmea -f - -o kml -F - " && vbm
"STATUS:Set cmd_conv_kml to:$cmd_conv_kml"; # convert NMEA to KML
1336 # MAIN LOOP:Record gps data until script lifespan ends
1337 timeBufferFirstNS
="$(timeEpochNS)"; bufferRound
=0; bufferTTL_AdjFloat
="$bufferTTL";
1338 while [[ "$SECONDS" -lt "$scriptTTL" ]]; do
1339 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
1340 magicParseRecipientDir
;
1341 magicGatherWriteBuffer
&
1342 sleep "$bufferTTL_AdjFloat"; # adjusted by magicBufferSleepPID
1344 magicBufferSleepPID
; # Calculates bufferTTL_AdjFloat from bufferTTL given buffer expected start time vs. actual
1349 try
rm -r "$dir_tmp" && vbm
"Removed dir_tmp:$dir_tmp";
1351 vbm
"STATUS:Main function finished.";
1353 #===END Declare local script functions===
1354 #==END Define script parameters==
1357 #==BEGIN Perform work and exit==
1358 main
"$@" # Run main function.
1360 #==END Perform work and exit==
1362 # Author: Steven Baltakatei Sandoval;