2 # Desc: Records gps data
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
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.6"; # 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.
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 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
="";
28 errReset
=0; BUFFER_TTL_ADJ_FLOAT
="";
30 #===BEGIN Declare local script functions===
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'
37 #echo "DEBUG:$(date +%S.%N)..Starting checkapp function."
38 #echo "DEBUG:args: $@"
39 #echo "DEBUG:returnState:$returnState"
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
49 appRollCall
[$arg]="false"; returnState
="false";
53 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
54 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
56 #===Determine function return code===
57 if [ "$returnState" = "true" ]; then
58 #echo "DEBUG:checkapp returns true for $arg";
61 #echo "DEBUG:checkapp returns false for $arg";
64 } # Check that app exists
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
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
81 fileRollCall
["$arg"]="false"; returnState
="false";
85 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done
86 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
88 #===Determine function return code===
89 if [ "$returnState" = "true" ]; then
90 #echo "DEBUG:checkapp returns true for $arg";
93 #echo "DEBUG:checkapp returns false for $arg";
96 } # Check that file exists
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
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";
119 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done
120 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
122 #===Determine function return code===
123 if [ "$returnState" = "true" ]; then
124 #echo "DEBUG:checkapp returns true for $arg";
127 #echo "DEBUG:checkapp returns false for $arg";
130 } # Check that dir exists
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 $*"; }
139 echo "$@" 1>&2; # Define stderr echo function.
140 } # Define stderr message function.
143 echoerr
" bkgpslog [ options ]"
146 echoerr
" -h, --help"
147 echoerr
" Display help information."
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\")"
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.
183 echoerr
"$SCRIPT_VERSION"
184 } # Display script version.
186 # Usage: vbm "DEBUG:verbose message here"
187 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
189 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
190 # - "$@" positional arguments fed to this function.
192 # Script function dependencies: echoerr
193 # External function dependencies: echo
194 # Last modified: 2020-04-11T23:57Z
195 # Last modified by: Steven Baltakatei Sandoval
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.
205 return 0; # Function finished.
206 } # Verbose message display function.
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:""$*"
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.
228 } # Argument Processing
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")
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
241 local tzDir returnState
242 if ! [[ $# -eq 1 ]]; then
243 yell
"ERROR:Invalid argument count.";
247 # Read TZDIR env var if available
248 if printenv TZDIR
1>/dev
/null
2>&1; then
249 tzDir
="$(printenv TZDIR)";
251 tzDir
="/usr/share/zoneinfo";
255 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
256 yell
"ERROR:Invalid time zone argument.";
259 # Export ARG1 as TZ environment variable
260 TZ
="$ARG1" && export TZ
&& returnState
="true";
263 # Determine function return code
264 if [ "$returnState" = "true" ]; then
267 } # Exports TZ environment variable
269 # Desc: Report seconds until next day.
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
277 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
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
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.";
292 try
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
294 # Determine function return code
295 if [[ "$returnState" = "true" ]]; then
297 elif [[ "$returnState" = "warning_zero" ]]; then
299 elif [[ "$returnState" = "warning_negative" ]]; then
302 } # Report seconds until next day
304 # Desc: Report seconds until next hour
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
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
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.";
325 try
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
327 # Determine function return code
328 if [[ "$returnState" = "true" ]]; then
330 elif [[ "$returnState" = "warning_zero" ]]; then
332 elif [[ "$returnState" = "warning_negative" ]]; then
335 } # Report seconds until next hour
337 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
338 # Usage: dateTimeShort ([str date])
340 # Input: arg1: 'date'-parsable timestamp string (optional)
341 # Output: stdout: timestamp (ISO-8601, no separators)
343 local TIME_CURRENT TIME_CURRENT_SHORT argTime
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";
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";
359 ### F: Time argument not valid; exit
360 yell
"ERROR:Invalid time argument supplied: \"$argTime\""; yell
"Exiting."; exit 1;
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
368 # Desc: Date without separators (YYYYmmdd)
369 # Usage: dateShort ([str date])
371 # Input: arg1: 'date'-parsable timestamp string (optional)
372 # Output: stdout: date (ISO-8601, no separators)
374 local TIME_CURRENT DATE_CURRENT_SHORT
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";
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";
390 ### F: Time argument not valid; exit
391 yell
"ERROR:Invalid time argument supplied: \"$argTime\""; yell
"Exiting."; exit 1;
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";
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])
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
417 argSeconds
="$1"; # read arg1 (seconds)
418 argPrecision
="$2"; # read arg2 (precision)
419 precision
=2; # set default precision
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
426 # Check that argSeconds provided
427 if [[ $# -ge 1 ]]; then
428 ## Check that argSeconds is a positive integer
429 if [[ "$argSeconds" =~ ^
[[:digit
:]]+$
]]; then
432 yell
"ERROR:argSeconds not a digit.";
433 returnState
="error_input";
436 yell
"ERROR:No argument provided. Exiting.";
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";
446 yell
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
447 returnState
="error_input";
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
480 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
481 witherPrecision
="false"
484 if $hasYears && [[ $precision -gt 0 ]]; then
486 witherPrecision
="true";
488 displayYears
="false";
490 if $witherPrecision; then ((precision--
)); fi;
493 if $hasMonths && [[ $precision -gt 0 ]]; then
494 displayMonths
="true";
495 witherPrecision
="true";
497 displayMonths
="false";
499 if $witherPrecision && [[ $precision -gt 0 ]]; then
500 displayMonths
="true";
502 if $witherPrecision; then ((precision--
)); fi;
505 if $hasDays && [[ $precision -gt 0 ]]; then
507 witherPrecision
="true";
511 if $witherPrecision && [[ $precision -gt 0 ]]; then
514 if $witherPrecision; then ((precision--
)); fi;
517 if $hasHours && [[ $precision -gt 0 ]]; then
519 witherPrecision
="true";
521 displayHours
="false";
523 if $witherPrecision && [[ $precision -gt 0 ]]; then
526 if $witherPrecision; then ((precision--
)); fi;
529 if $hasMinutes && [[ $precision -gt 0 ]]; then
530 displayMinutes
="true";
531 witherPrecision
="true";
533 displayMinutes
="false";
535 if $witherPrecision && [[ $precision -gt 0 ]]; then
536 displayMinutes
="true";
538 if $witherPrecision; then ((precision--
)); fi;
542 if $hasSeconds && [[ $precision -gt 0 ]]; then
543 displaySeconds
="true";
544 witherPrecision
="true";
546 displaySeconds
="false";
548 if $witherPrecision && [[ $precision -gt 0 ]]; then
549 displaySeconds
="true";
551 if $witherPrecision; then ((precision--
)); fi;
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
557 ## Construct duration output string
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
574 ## Output duration string to stdout
575 echo "$OUTPUT" && returnState
="true";
577 #===Determine function return code===
578 if [ "$returnState" = "true" ]; then
580 elif [ "$returnState" = "error_input" ]; then
584 yell
"ERROR:Unknown";
588 } # Get duration (ex: PT10M4S )
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 "
606 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
607 echo "$missingApps" 1>&2;
609 #===END Display Missing Apps===
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 "
622 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
623 echo "$missingFiles" 1>&2;
625 #===END Display Missing Files===
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 "
638 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
639 echo "$missingDirs" 1>&2;
641 #===END Display Missing Directories===
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
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.
659 yell
"ERROR: timeUntilNextDay exit code $?"; exit 1;
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.
668 yell
"ERROR: timeUntilNextHour exit code $?"; exit 1;
672 yell
"ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
674 } # Seconds until next (day|hour).
676 # Desc: Checks that a valid tar archive exists, creates one otherwise
677 # Usage: checkMakeTar [ path ]
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
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";
692 ## F1: Check if file exists
693 if [[ -f "$PATH_TAR" ]]; then
695 try
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
696 returnFlag1
="tar moved";
701 ## F2: Create tar archive, return 0
702 try
tar --create --file="$PATH_TAR" --files-from=/dev
/null
&& \
703 returnFlag2
="tar created";
706 # Determine function return code
707 if [[ "$returnFlag0" = "tar valid" ]]; then
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
714 } # checks if arg1 is tar; creates one otherwise
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]...)
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" &
730 # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533
733 local FN
="${FUNCNAME[0]}";
734 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
737 if ! [ -z "$2" ]; then FILENAME
="$2"; else yell
"ERROR:$FN:Not enough arguments."; exit 1; fi
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
743 if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell
"ERROR:$FN:No temporary working dir set."; exit 1; fi
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
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"
764 # Write to temporary working dir
765 eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME";
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
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]...)
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" &
788 local FN
="${FUNCNAME[0]}";
789 #yell "DEBUG:STATUS:$FN:Finished appendFileTar()."
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
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
803 # Input command string
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"
816 # Write to temporary working dir
817 eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME";
820 try
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
821 #yell "DEBUG:STATUS:$FN:Finished appendFileTar()."
822 } # Append file to Tar archive
824 # Desc: Checks if string is an age-compatible pubkey
825 # Usage: checkAgePubkey [str pubkey]
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 )
835 if echo "test" | age
-a -r "$argPubkey" 1>/dev
/null
; then
842 # Desc: Validates Input
843 # Usage: validateInput [str input] [str input type]
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
851 local FN
="${FUNCNAME[0]}";
856 if [[ $# -gt 2 ]]; then yell
"ERROR:$0:$FN:Too many arguments."; exit 1; fi;
859 if [[ -z "$argInput" ]]; then return 1; fi
863 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
864 if [[ "$argType" = "ssh_pubkey" ]]; then
865 if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\
]*[[:alnum
:]+/=]*$
]]; then
869 ### Check for age1[:bech32:]
870 if [[ "$argType" = "age_pubkey" ]]; then
871 if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$
]]; then
875 if [[ "$argType" = "integer" ]]; then
876 if [[ "$argInput" =~ ^
[[:digit
:]]*$
]]; then
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
890 # Return error if no condition matched.
892 } # Validates strings
894 # Desc: Get epoch nanoseconds
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
907 TIME_CURRENT
="$(date --iso-8601=ns)"; # Produce `date`-parsable current timestamp with resolution of 1 nanosecond.
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";
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";
920 ### F: Time argument not valid; exit
921 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
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
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
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";
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
953 # Calculate Error, errNS, in nanoseconds
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[@]}";
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 ) ));
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";
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";
986 vbm
"errResetTerm:$((errReset/T_I))";
987 vbm
"errRateTerm :$((errRate*T_D))";
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";
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.";
1004 ## Calculate nanosecond remainder
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
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
1023 # Set VERSION file name
1024 FILEOUT_VERSION
="$(dateTimeShort)..VERSION";
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
1038 CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1040 ## Process newline escapes
1041 CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")"
1043 # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR
1044 appendArgTar
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$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 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()
1056 vbm
"DEBUG:STATUS:$FN:Started magicGatherWriteBuffer().";
1057 # Debug:Get function name
1058 FN
="${FUNCNAME[0]}";
1060 # Create buffer file with unique name
1061 PATHOUT_BUFFER
="$DIR_TMP/buffer$SECONDS" && vbm
"PATHOUT_BUFFER:$PATHOUT_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";
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";
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
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
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
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\"";
1136 ###### F: throw warning;
1137 yell
"ERROR:Invalid recipient file detected. Not modifying recipient list."
1138 updateRecipients
="false";
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[*]}\"";
1146 yell
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
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()
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";
1176 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
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
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";
1187 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
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."
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";
1211 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
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.";
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
1230 if [[ "$OPTION_TMPDIR" = "true" ]]; then
1231 if [[ -d "$argTempDirPriority" ]]; then
1232 DIR_TMP_PARENT
="$argTempDirPriority";
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.
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";
1245 yell
"ERROR:No valid working directory available. Exiting.";
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
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";
1267 ### F: argcustomBufferTTL is not an integer
1268 yell
"ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1;
1270 ## F: do not change BUFFER_TTL
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";
1280 ### F: argcustomScriptTTL is not a time element
1281 yell
"ERROR:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1;
1283 ## F: do not change SCRIPT_TTL_TE
1285 } # Sets custom script or buffer TTL if specified
1289 # DEBUG: Print environment variables
1290 vbm
"echo $(printenv)";
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
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
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
1315 # File name substring (ISO-8601 duration from BUFFER_TTL)
1316 bufferTTL_STR
="$(timeDuration "$BUFFER_TTL")";
1318 # Init temp working dir
1319 try mkdir
"$DIR_TMP" && vbm
"DEBUG:Working dir creatd at:$DIR_TMP";
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
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
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 magicParseRecipientDir
;
1339 magicGatherWriteBuffer
&
1340 sleep "$BUFFER_TTL_ADJ_FLOAT"; # adjusted by magicBufferSleepPID
1342 magicBufferSleepPID
; # Calculates BUFFER_TTL_ADJ_FLOAT from BUFFER_TTL given buffer expected start time vs. actual
1347 try
rm -r "$DIR_TMP";
1349 vbm
"STATUS:Main function finished.";
1351 #===END Declare local script functions===
1352 #==END Define script parameters==
1355 #==BEGIN Perform work and exit==
1356 main
"$@" # Run main function.
1358 #==END Perform work and exit==
1360 # Author: Steven Baltakatei Sandoval;