3 # Desc: Records gps data until midnight
4 # Author: Steven Baltakatei Sandoval; License: GPLv3+
5 # Usage: bkgpslog -o [output dir]
7 #==BEGIN Define script parameters==
8 ## Logging Behavior parameters
9 BUFFER_TTL
="300"; # time between file writes
10 SCRIPT_TTL_TE
="day"; # (day|hour)
11 #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option
12 DIR_TMP_DEFAULT
="/dev/shm"; # Default parent of working directory
14 SCRIPT_TIME_START
=$
(date +%Y
%m
%dT
%H
%M
%S.
%N
);
15 PATH
="$HOME/.local/bin:$PATH"; # Add "$(systemd-path user-binaries)" path in case apps saved there
16 SCRIPT_HOSTNAME
=$
(hostname
); # Save hostname of system running this script.
17 SCRIPT_VERSION
="0.5.3"; # Define version of script.
18 SCRIPT_NAME
="bkgpslog"; # Define basename of script file.
19 SCRIPT_URL
="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script.
20 AGE_VERSION
="1.0.0-beta2"; # Define version of age (encryption program)
21 AGE_URL
="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age.
23 declare -Ag appRollCall
# Associative array for storing app status
24 declare -Ag fileRollCall
# Associative array for storing file status
25 declare -Ag dirRollCall
# Associative array for storing dir status
26 declare -a argRecPubKeys
# for processArguments function
27 # declare -a errorHistory # for correcting buffer lag
29 ## Initialize variables
30 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
="";
31 errReset
=0; BUFFER_TTL_ADJ_FLOAT
="";
33 #===BEGIN Declare local script functions===
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 # Yell, Die, Try Three-Fingered Claw technique
136 # Ref/Attrib: https://stackoverflow.com/a/25515370
137 yell
() { echo "$0: $*" >&2; }
138 die
() { yell
"$*"; exit 111; }
139 try
() { "$@" || die
"cannot $*"; }
142 echo "$@" 1>&2; # Define stderr echo function.
143 } # Define stderr message function.
146 echoerr
" bkgpslog [ options ]"
149 echoerr
" -h, --help"
150 echoerr
" Display help information."
152 echoerr
" Display script version."
153 echoerr
" -v, --verbose"
154 echoerr
" Display debugging info."
155 echoerr
" -e, --encrypt"
156 echoerr
" Encrypt output."
157 echoerr
" -r, --recipient [ string pubkey ]"
158 echoerr
" Specify recipient. May be age or ssh pubkey."
159 echoerr
" May be specified multiple times for multiple pubkeys."
160 echoerr
" See https://github.com/FiloSottile/age"
161 echoerr
" -o, --output [ path dir ]"
162 echoerr
" Specify output directory to save logs."
163 echoerr
" -c, --compress"
164 echoerr
" Compress output with gzip (before encryption if enabled)."
165 echoerr
" -z, --time-zone"
166 echoerr
" Specify time zone. (ex: \"America/New_York\")"
167 echoerr
" -t, --temp-dir [path dir]"
168 echoerr
" Specify parent directory for temporary working directory."
169 echoerr
" Default: \"/dev/shm\""
170 echoerr
" -R, --recipient-dir [path dir]"
171 echoerr
" Specify directory containing files whose first lines are"
172 echoerr
" to be interpreted as pubkey strings (see \\'-r\\' option)."
173 echoerr
" -b, --buffer-ttl [integer]"
174 echoerr
" Specify custom buffer period in seconds (default: 300 seconds)"
175 echoerr
" -B, --script-ttl [integer]"
176 echoerr
" Specify custom script time-to-live in seconds (default: \"day\")"
178 echoerr
"EXAMPLE: (bash script lines)"
179 echoerr
"/bin/bash bkgpslog -v -e -c \\"
180 echoerr
"-z \"UTC\" -t \"/dev/shm\" \\"
181 echoerr
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
182 echoerr
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
183 echoerr
"-o ~/Sync/Location"
184 } # Display information on how to use this script.
186 echoerr
"$SCRIPT_VERSION"
187 } # Display script version.
189 # Usage: vbm "DEBUG:verbose message here"
190 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
192 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
193 # - "$@" positional arguments fed to this function.
195 # Script function dependencies: echoerr
196 # External function dependencies: echo
197 # Last modified: 2020-04-11T23:57Z
198 # Last modified by: Steven Baltakatei Sandoval
202 if [ "$OPTION_VERBOSE" = "true" ]; then
203 FUNCTION_TIME
=$
(date --iso-8601=ns
); # Save current time in nano seconds.
204 echoerr
"[$FUNCTION_TIME] ""$*"; # Display argument text.
208 return 0; # Function finished.
209 } # Verbose message display function.
211 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
212 #echoerr "DEBUG:Starting processArguments while loop."
213 #echoerr "DEBUG:Provided arguments are:""$*"
215 -h |
--help) showUsage
; exit 1;; # Display usage.
216 --version) showVersion
; exit 1;; # Show version
217 -v |
--verbose) OPTION_VERBOSE
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode.
218 -o |
--output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory.
219 -e |
--encrypt) OPTION_ENCRYPT
="true"; vbm
"DEBUG:Encrypted output mode enabled.";; # Enable encryption
220 -r |
--recipient) OPTION_RECIPIENTS
="true"; argRecPubKeys
+=("$2"); vbm
"STATUS:pubkey added:""$2"; shift;; # Add recipients
221 -c |
--compress) OPTION_COMPRESS
="true"; vbm
"DEBUG:Compressed output mode enabled.";; # Enable compression
222 -z |
--time-zone) try setTimeZoneEV
"$2"; shift;; # Set timestamp timezone
223 -t |
--temp-dir) OPTION_TMPDIR
="true" && argTempDirPriority
="$2"; shift;; # Set time zone
224 -R |
--recipient-dir) OPTION_RECIPIENTS
="true"; OPTION_RECDIR
="true" && argRecDir
="$2"; shift;; # Add recipient watch dir
225 -b |
--buffer-ttl) OPTION_CUSTOM_BUFFERTTL
="true" && argCustomBufferTTL
="$2"; shift;; # Set custom buffer period (default: 300 seconds)
226 -B |
--script-ttl) OPTION_CUSTOM_SCRIPTTTL_TE
="true" && argCustomScriptTTL
="$2"; shift;; # Set custom script TTL (default: "day")
227 *) echoerr
"ERROR: Unrecognized argument: $1"; echoerr
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
231 } # Argument Processing
233 # Desc: Set time zone environment variable TZ
234 # Usage: setTimeZoneEV arg1
235 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
236 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
238 # exit code 0 on success
239 # exit code 1 on incorrect number of arguments
240 # exit code 2 if unable to validate arg1
241 # Depends: yell, printenv, bash 5
242 # Tested on: Debian 10
244 local tzDir returnState
245 if ! [[ $# -eq 1 ]]; then
246 yell
"ERROR:Invalid argument count.";
250 # Read TZDIR env var if available
251 if printenv TZDIR
1>/dev
/null
2>&1; then
252 tzDir
="$(printenv TZDIR)";
254 tzDir
="/usr/share/zoneinfo";
258 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
259 yell
"ERROR:Invalid time zone argument.";
262 # Export ARG1 as TZ environment variable
263 TZ
="$ARG1" && export TZ
&& returnState
="true";
266 # Determine function return code
267 if [ "$returnState" = "true" ]; then
270 } # Exports TZ environment variable
272 # Desc: Report seconds until next day.
274 # Output: stdout: integer seconds until next day
275 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
276 # Usage: timeUntilNextDay
277 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
278 # Depends: date 8, echo 8, yell, try
280 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
282 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
283 TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
284 SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
285 if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then
287 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then
288 returnState
="warning_zero";
289 yell
"WARNING:Reported time until next day exactly zero.";
290 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then
291 returnState
="warning_negative";
292 yell
"WARNING:Reported time until next day is negative.";
295 try
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
297 # Determine function return code
298 if [[ "$returnState" = "true" ]]; then
300 elif [[ "$returnState" = "warning_zero" ]]; then
302 elif [[ "$returnState" = "warning_negative" ]]; then
305 } # Report seconds until next day
307 # Desc: Report seconds until next hour
309 # Output: stdout: integer seconds until next hour
310 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
311 # Usage: timeUntilNextHour
312 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
314 local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
315 TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
316 TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
317 SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second).
318 if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then
320 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then
321 returnState
="warning_zero";
322 yell
"WARNING:Reported time until next hour exactly zero.";
323 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then
324 returnState
="warning_negative";
325 yell
"WARNING:Reported time until next hour is negative.";
328 try
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
330 # Determine function return code
331 if [[ "$returnState" = "true" ]]; then
333 elif [[ "$returnState" = "warning_zero" ]]; then
335 elif [[ "$returnState" = "warning_negative" ]]; then
338 } # Report seconds until next hour
340 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
341 # Usage: dateTimeShort ([str date])
343 # Input: arg1: 'date'-parsable timestamp string (optional)
344 # Output: stdout: timestamp (ISO-8601, no separators)
346 local TIME_CURRENT TIME_CURRENT_SHORT
350 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
351 # Decide to parse current or supplied date
352 ## Check if time argument empty
353 if [[ -z "$argTime" ]]; then
354 ## T: Time argument empty, use current time
355 TIME_INPUT
="$TIME_CURRENT";
357 ## F: Time argument exists, validate time
358 if date --date="$argTime" 1>/dev
/null
2>&1; then
359 ### T: Time argument is valid; use it
360 TIME_INPUT
="$argTime";
362 ### F: Time argument not valid; exit
363 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
366 # Construct and deliver separator-les date string
367 TIME_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)";
368 echo "$TIME_CURRENT_SHORT";
369 } # Get YYYYmmddTHHMMSS±zzzz
371 # Desc: Date without separators (YYYYmmdd)
372 # Usage: dateShort ([str date])
374 # Input: arg1: 'date'-parsable timestamp string (optional)
375 # Output: stdout: date (ISO-8601, no separators)
377 local TIME_CURRENT DATE_CURRENT_SHORT
381 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
382 # Decide to parse current or supplied date
383 ## Check if time argument empty
384 if [[ -z "$argTime" ]]; then
385 ## T: Time argument empty, use current time
386 TIME_INPUT
="$TIME_CURRENT";
388 ## F: Time argument exists, validate time
389 if date --date="$argTime" 1>/dev
/null
2>&1; then
390 ### T: Time argument is valid; use it
391 TIME_INPUT
="$argTime";
393 ### F: Time argument not valid; exit
394 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
397 # Construct and deliver separator-les date string
398 DATE_CURRENT_SHORT
="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
399 echo "$DATE_CURRENT_SHORT";
402 # Desc: Given seconds, output ISO-8601 duration string
403 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
404 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
405 # Usage: timeDuration [1:seconds] ([2:precision])
407 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
408 # arg2: precision level (optional; default=2)
409 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
410 # exit code 0: success
411 # exit code 1: error_input
412 # exit code 2: error_unknown
413 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
414 # Depends: date 8 (gnucoreutils), yell,
415 local returnState argSeconds argPrecision remainder precision witherPrecision
416 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
417 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
418 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
420 argSeconds
="$1"; # read arg1 (seconds)
421 argPrecision
="$2"; # read arg2 (precision)
422 precision
=2; # set default precision
424 # Check that between one and two arguments is supplied
425 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
426 yell
"ERROR:Invalid number of arguments:$# . Exiting.";
427 returnState
="error_input"; fi
429 # Check that argSeconds provided
430 if [[ $# -ge 1 ]]; then
431 ## Check that argSeconds is a positive integer
432 if [[ "$argSeconds" =~ ^
[[:digit
:]]+$
]]; then
435 yell
"ERROR:argSeconds not a digit.";
436 returnState
="error_input";
439 yell
"ERROR:No argument provided. Exiting.";
443 # Consider whether argPrecision was provided
444 if [[ $# -eq 2 ]]; then
445 # Check that argPrecision is a positive integer
446 if [[ "$argPrecision" =~ ^
[[:digit
:]]+$
]] && [[ "$argPrecision" -gt 0 ]]; then
447 precision
="$argPrecision";
449 yell
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
450 returnState
="error_input";
456 remainder
="$argSeconds" ; # seconds
457 ## Calculate full years Y, update remainder
458 fullYears
=$
(( remainder
/ (365*24*60*60) ));
459 remainder
=$
(( remainder
- (fullYears
*365*24*60*60) ));
460 ## Calculate full months M, update remainder
461 fullMonths
=$
(( remainder
/ (30*24*60*60) ));
462 remainder
=$
(( remainder
- (fullMonths
*30*24*60*60) ));
463 ## Calculate full days D, update remainder
464 fullDays
=$
(( remainder
/ (24*60*60) ));
465 remainder
=$
(( remainder
- (fullDays
*24*60*60) ));
466 ## Calculate full hours H, update remainder
467 fullHours
=$
(( remainder
/ (60*60) ));
468 remainder
=$
(( remainder
- (fullHours
*60*60) ));
469 ## Calculate full minutes M, update remainder
470 fullMinutes
=$
(( remainder
/ (60) ));
471 remainder
=$
(( remainder
- (fullMinutes
*60) ));
472 ## Calculate full seconds S, update remainder
473 fullSeconds
=$
(( remainder
/ (1) ));
474 remainder
=$
(( remainder
- (remainder
*1) ));
475 ## Check which fields filled
476 if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi
477 if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi
478 if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi
479 if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi
480 if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi
481 if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi
483 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
484 witherPrecision
="false"
487 if $hasYears && [[ $precision -gt 0 ]]; then
489 witherPrecision
="true";
491 displayYears
="false";
493 if $witherPrecision; then ((precision--
)); fi;
496 if $hasMonths && [[ $precision -gt 0 ]]; then
497 displayMonths
="true";
498 witherPrecision
="true";
500 displayMonths
="false";
502 if $witherPrecision && [[ $precision -gt 0 ]]; then
503 displayMonths
="true";
505 if $witherPrecision; then ((precision--
)); fi;
508 if $hasDays && [[ $precision -gt 0 ]]; then
510 witherPrecision
="true";
514 if $witherPrecision && [[ $precision -gt 0 ]]; then
517 if $witherPrecision; then ((precision--
)); fi;
520 if $hasHours && [[ $precision -gt 0 ]]; then
522 witherPrecision
="true";
524 displayHours
="false";
526 if $witherPrecision && [[ $precision -gt 0 ]]; then
529 if $witherPrecision; then ((precision--
)); fi;
532 if $hasMinutes && [[ $precision -gt 0 ]]; then
533 displayMinutes
="true";
534 witherPrecision
="true";
536 displayMinutes
="false";
538 if $witherPrecision && [[ $precision -gt 0 ]]; then
539 displayMinutes
="true";
541 if $witherPrecision; then ((precision--
)); fi;
545 if $hasSeconds && [[ $precision -gt 0 ]]; then
546 displaySeconds
="true";
547 witherPrecision
="true";
549 displaySeconds
="false";
551 if $witherPrecision && [[ $precision -gt 0 ]]; then
552 displaySeconds
="true";
554 if $witherPrecision; then ((precision--
)); fi;
556 ## Determine whether or not the "T" separator is needed to separate date and time elements
557 if ( $displayHours ||
$displayMinutes ||
$displaySeconds); then
558 displayDateTime
="true"; else displayDateTime
="false"; fi
560 ## Construct duration output string
562 if $displayYears; then
563 OUTPUT
=$OUTPUT$fullYears"Y"; fi
564 if $displayMonths; then
565 OUTPUT
=$OUTPUT$fullMonths"M"; fi
566 if $displayDays; then
567 OUTPUT
=$OUTPUT$fullDays"D"; fi
568 if $displayDateTime; then
569 OUTPUT
=$OUTPUT"T"; fi
570 if $displayHours; then
571 OUTPUT
=$OUTPUT$fullHours"H"; fi
572 if $displayMinutes; then
573 OUTPUT
=$OUTPUT$fullMinutes"M"; fi
574 if $displaySeconds; then
575 OUTPUT
=$OUTPUT$fullSeconds"S"; fi
577 ## Output duration string to stdout
578 echo "$OUTPUT" && returnState
="true";
580 #===Determine function return code===
581 if [ "$returnState" = "true" ]; then
583 elif [ "$returnState" = "error_input" ]; then
587 yell
"ERROR:Unknown";
591 } # Get duration (ex: PT10M4S )
593 # Desc: Displays missing apps, files, and dirs
594 # Usage: displayMissing
595 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
596 # Output: stderr messages
597 #==BEGIN Display errors==
598 #===BEGIN Display Missing Apps===
599 missingApps
="Missing apps :"
600 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
601 for key
in "${!appRollCall[@]}"; do
602 value
="${appRollCall[$key]}"
603 if [ "$value" = "false" ]; then
604 #echo "DEBUG:Missing apps: $key => $value";
605 missingApps
="$missingApps""$key "
609 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
610 echo "$missingApps" 1>&2;
612 #===END Display Missing Apps===
614 #===BEGIN Display Missing Files===
615 missingFiles
="Missing files:"
616 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
617 for key
in "${!fileRollCall[@]}"; do
618 value
="${fileRollCall[$key]}"
619 if [ "$value" = "false" ]; then
620 #echo "DEBUG:Missing files: $key => $value";
621 missingFiles
="$missingFiles""$key "
625 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
626 echo "$missingFiles" 1>&2;
628 #===END Display Missing Files===
630 #===BEGIN Display Missing Directories===
631 missingDirs
="Missing dirs:"
632 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
633 for key
in "${!dirRollCall[@]}"; do
634 value
="${dirRollCall[$key]}"
635 if [ "$value" = "false" ]; then
636 #echo "DEBUG:Missing dirs: $key => $value";
637 missingDirs
="$missingDirs""$key "
641 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
642 echo "$missingDirs" 1>&2;
644 #===END Display Missing Directories===
646 #==END Display errors==
647 } # Display missing apps, files, dirs
648 magicSetScriptTTL
() {
649 #Desc: Sets script_TTL seconds from provided time_element string argument
650 #Usage: magicSetScriptTTL [str time_element]
651 #Input: arg1: string (Ex: SCRIPT_TTL_TE; "day" or "hour")
652 #Output: var: SCRIPT_TTL (integer seconds)
653 #Depends: timeUntilNextHour, timeUntilNextDay
656 if [[ "$argTimeElement" = "day" ]]; then
657 # Set script lifespan to end at start of next day
658 if ! SCRIPT_TTL
="$(timeUntilNextDay)"; then
659 if [[ "$SCRIPT_TTL" -eq 0 ]]; then
660 ((SCRIPT_TTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
662 yell
"ERROR: timeUntilNextDay exit code $?"; exit 1;
665 elif [[ "$argTimeElement" = "hour" ]]; then
666 # Set script lifespan to end at start of next hour
667 if ! SCRIPT_TTL
="$(timeUntilNextHour)"; then
668 if [[ "$SCRIPT_TTL" -eq 0 ]]; then
669 ((SCRIPT_TTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
671 yell
"ERROR: timeUntilNextHour exit code $?"; exit 1;
675 yell
"ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
677 } # Seconds until next (day|hour).
679 # Desc: Checks that a valid tar archive exists, creates one otherwise
680 # Usage: checkMakeTar [ path ]
682 # Input: arg1: path of tar archive
683 # Output: exit code 0 : tar readable
684 # exit code 1 : tar missing; created
685 # exit code 2 : tar not readable; moved; replaced
686 # Depends: try, tar, date
687 local PATH_TAR returnFlag0 returnFlag1 returnFlag2
690 # Check if file is a valid tar archive
691 if tar --list --file="$PATH_TAR" 1>/dev
/null
2>&1; then
692 ## T1: return success
693 returnFlag0
="tar valid";
695 ## F1: Check if file exists
696 if [[ -f "$PATH_TAR" ]]; then
698 try
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
699 returnFlag1
="tar moved";
704 ## F2: Create tar archive, return 0
705 try
tar --create --file="$PATH_TAR" --files-from=/dev
/null
&& \
706 returnFlag2
="tar created";
709 # Determine function return code
710 if [[ "$returnFlag0" = "tar valid" ]]; then
712 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
713 return 1; # tar missing so created
714 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
715 return 2; # tar not readable so moved; replaced
717 } # checks if arg1 is tar; creates one otherwise
719 # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar
720 # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
722 # Input: arg1: data to be written
723 # arg2: file name of file to be inserted into tar
724 # arg3: tar archive path (must exist first)
725 # arg4: temporary working dir
726 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
727 # Output: file written to disk
728 # Example: decrypt multiple large files in parallel
729 # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
730 # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
731 # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
733 # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533
736 local FN
="${FUNCNAME[0]}";
737 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
740 if ! [ -z "$2" ]; then FILENAME
="$2"; else yell
"ERROR:$FN:Not enough arguments."; exit 1; fi
742 # Check tar path is a file
743 if [ -f "$3" ]; then TAR_PATH
="$3"; else yell
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi
746 if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell
"ERROR:$FN:No temporary working dir set."; exit 1; fi
748 # Set command strings
749 if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1
750 if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2
751 if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3
752 if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4
758 # yell "DEBUG:STATUS:$FN:CMD0:$CMD0"
759 # yell "DEBUG:STATUS:$FN:CMD1:$CMD1"
760 # yell "DEBUG:STATUS:$FN:CMD2:$CMD2"
761 # yell "DEBUG:STATUS:$FN:CMD3:$CMD3"
762 # yell "DEBUG:STATUS:$FN:CMD4:$CMD4"
763 # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME"
764 # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH"
765 # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR"
767 # Write to temporary working dir
768 eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME";
771 try
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
772 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
773 } # Append Bash var to file appended to Tar archive
775 # Desc: Processes first file and then appends to tar
776 # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
778 # Input: arg1: path of file to be (processed and) written
779 # arg2: name to use for file inserted into tar
780 # arg3: tar archive path (must exist first)
781 # arg4: temporary working dir
782 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
783 # Output: file written to disk
784 # Example: decrypt multiple large files in parallel
785 # appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
786 # appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
787 # appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
791 local FN
="${FUNCNAME[0]}";
792 #yell "DEBUG:STATUS:$FN:Finished appendFileTar()."
795 if ! [ -z "$2" ]; then FILENAME
="$2"; else yell
"ERROR:$FN:Not enough arguments."; exit 1; fi
796 # Check tar path is a file
797 if [ -f "$3" ]; then TAR_PATH
="$3"; else yell
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi
799 if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell
"ERROR:$FN:No temporary working dir set."; exit 1; fi
800 # Set command strings
801 if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1
802 if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2
803 if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3
804 if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4
806 # Input command string
810 # yell "DEBUG:STATUS:$FN:CMD0:$CMD0"
811 # yell "DEBUG:STATUS:$FN:CMD1:$CMD1"
812 # yell "DEBUG:STATUS:$FN:CMD2:$CMD2"
813 # yell "DEBUG:STATUS:$FN:CMD3:$CMD3"
814 # yell "DEBUG:STATUS:$FN:CMD4:$CMD4"
815 # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME"
816 # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH"
817 # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR"
819 # Write to temporary working dir
820 eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME";
823 try
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
824 #yell "DEBUG:STATUS:$FN:Finished appendFileTar()."
825 } # Append file to Tar archive
827 # Desc: Checks if string is an age-compatible pubkey
828 # Usage: checkAgePubkey [str pubkey]
830 # Input: arg1: string
831 # Output: return code 0: string is age-compatible pubkey
832 # return code 1: string is NOT an age-compatible pubkey
833 # age stderr (ex: there is stderr if invalid string provided)
834 # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 )
838 if echo "test" | age
-a -r "$argPubkey" 1>/dev
/null
; then
845 # Desc: Validates Input
846 # Usage: validateInput [str input] [str input type]
848 # Input: arg1: string to validate
849 # arg2: string specifying input type (ex:"ssh_pubkey")
850 # Output: return code 0: if input string matched specified string type
851 # Depends: bash 5, yell
854 local FN
="${FUNCNAME[0]}";
859 if [[ $# -gt 2 ]]; then yell
"ERROR:$0:$FN:Too many arguments."; exit 1; fi;
862 if [[ -z "$argInput" ]]; then return 1; fi
866 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
867 if [[ "$argType" = "ssh_pubkey" ]]; then
868 if [[ "$argInput" =~ ^
[[:alnum
:]-]*[\
]*[[:alnum
:]+/=]*$
]]; then
872 ### Check for age1[:bech32:]
873 if [[ "$argType" = "age_pubkey" ]]; then
874 if [[ "$argInput" =~ ^age1
[qpzry9x8gf2tvdw0s3jn54khce6mua7l
]*$
]]; then
878 if [[ "$argType" = "integer" ]]; then
879 if [[ "$argInput" =~ ^
[[:digit
:]]*$
]]; then
882 ## time element (year, month, week, day, hour, minute, second)
883 if [[ "$argType" = "time_element" ]]; then
884 if [[ "$argInput" = "year" ]] || \
885 [[ "$argInput" = "month" ]] || \
886 [[ "$argInput" = "week" ]] || \
887 [[ "$argInput" = "day" ]] || \
888 [[ "$argInput" = "hour" ]] || \
889 [[ "$argInput" = "minute" ]] || \
890 [[ "$argInput" = "second" ]]; then
893 # Return error if no condition matched.
895 } # Validates strings
897 # Desc: Get epoch nanoseconds
900 # Input: arg1: 'date'-parsable timestamp string (optional)
901 # Output: Nanoseconds since 1970-01-01
902 # Depends: date 8, yell()
903 # Ref/Attrib: Force base 10 Bash arith with '10#'. https://stackoverflow.com/a/24777667
904 local TIME_CURRENT TIME_INPUT TIME_EPOCH_FLOAT TIME_EPOCH_NSFRAC
910 TIME_CURRENT
="$(date --iso-8601=ns)"; # Produce `date`-parsable current timestamp with resolution of 1 nanosecond.
912 # Decide to parse current or supplied time
913 ## Check if time argument empty
914 if [[ -z "$argTime" ]]; then
915 ## T: Time argument empty, use current time
916 TIME_INPUT
="$TIME_CURRENT";
918 ## F: Time argument exists, validate time
919 if date --date="$argTime" 1>/dev
/null
2>&1; then
920 ### T: Time argument is valid; use it
921 TIME_INPUT
="$argTime";
923 ### F: Time argument not valid; exit
924 yell
"ERROR:Invalid time argument supplied. Exiting."; exit 1;
927 # Construct and deliver nanoseconds since 1970-01-01
928 TIME_EPOCH_FLOAT
="$(date --date="$TIME_INPUT" +%s.%N)"; # Save ssss.NNNNNNNNN
929 TIME_EPOCH_INT
="$(echo "$TIME_EPOCH_FLOAT" | cut -d. -f1)"; # Get ssss
930 TIME_EPOCH_NSFRAC
="$(echo "$TIME_EPOCH_FLOAT" | cut -d. -f2)"; # Get NNNNNNNNN
931 TIME_EPOCH_NS
="$(( (10#"$TIME_EPOCH_INT" * 10**9) + (10#"$TIME_EPOCH_NSFRAC") ))";
932 echo "$TIME_EPOCH_NS";
933 } # Nanoseconds since 1970-01-01
934 magicBufferSleepPID
() {
935 # Desc: Compensates for lag so buffer rounds start every BUFFER_TTL seconds
936 # Input: vars: BUFFER_TTL, errResetx10e3, K_P, T_I, T_D
937 # # Input: array: errorHistory
938 # Output: vars: BUFFER_TTL_ADJ_FLOAT
939 # Re/Attrib: https://en.wikipedia.org/wiki/PID_controller#Standard_versus_parallel_(ideal)_form
941 local timeBufferStartNS timeBufferStartNSExp errNS errNSx10e3
942 local errResetx10e3 errRatex10e3 ADJ BUFFER_TTL_ADJ_NS BUFFER_TTL_ADJ_INT
943 local BUFFER_TTL_ADJ_FLOATFRAC
944 # local errorHistorySize
946 # ## Define errorHistorySize
947 # errorHistorySize=100;
948 ## Define BUFFER_TTL in nanoseconds
949 BUFFER_TTL_NS
=$
((BUFFER_TTL
* 10**9)) && vbm
"BUFFER_TTL_NS:$BUFFER_TTL_NS";
951 ### PID Control factors
952 K_P
=1; # Gain for compensating buffer round lag
953 T_I
="$(((4)*BUFFER_TTL_NS/(1)))"; # Consider this number of past nanoseconds to eliminate error
954 T_D
="$(((1)*BUFFER_TTL_NS/(1)))"; # Predict value this number of nanoseconds into the future
956 # Calculate Error, errNS, in nanoseconds
958 timeBufferStartNS
="$(timeEpochNS)" && vbm
"timeBufferStartNS :$timeBufferStartNS";
959 ## Calculate expected time (from start time, current buffer round number, nominal BUFFER_TTL)
960 timeBufferStartNSExp
="$(( (timeBufferFirstNS) + (BUFFER_TTL_NS * bufferRound) ))" && vbm
"timeBufferStartNSExp:$timeBufferStartNSExp";
961 ## Calculate error (diff between timeBufferStartNSExp and timeBufferStartNS; usually negative)
962 errNS
="$(( timeBufferStartNSExp - timeBufferStartNS ))" && vbm
"errNS:$errNS";
963 # errNSx10e3="$((errNS*10**3))" && vbm "errNSx10e3:$errNSx10e3";
964 # ## Append error to errorHistory
965 # errorHistory+=("errNS");
966 # ### Trim errorHistory array if over errorHistorySize
967 # while [[ "${#errorHistory[@]}" -gt "errorHistorySize" ]]; then do
968 # unset "errorHistory[0]"; # remove oldest entry, creating sparse array
969 # errorHistory=("${errorHistory[@]}"); # reindex sparse array
970 # vbm "STATUS:Trimmed errorHistory array. Entry count:${#errorHistory[@]}";
973 # Calculate errReset in nanoseconds^2
974 ## errReset = int(errHistory(t),wrt(delta_BUFFER_TTL))
975 ## Integrate errorHistory with respect to time
976 # for value in "${errorHistory[@]}"; do
977 # errReset=$(( errReset + ( value*BUFFER_TTL_NS ) ));
979 vbm
"errReset(orig):$errReset"
980 errReset
="$(( (errReset + (errNS*BUFFER_TTL_NS)) ))" && vbm
"errReset(post):$errReset";
981 # errResetx10e3="$(( ( errResetx10e3 + ( errNSx10e3 * BUFFER_TTL_NS ) )*10**3 ))" && vbm "errResetx10e3:$errResetx10e3";
983 # Calculate errRate in nanoseconds per nanosecond
984 errRate
="$(( errNS / BUFFER_TTL_NS ))" && vbm
"errRate:$errRate";
985 # errRatex10e3="$(( ( errNSx10e3 ) / BUFFER_TTL_NS ))" && vbm "errRatex10e3:$errRatex10e3";
989 vbm
"errResetTerm:$((errReset/T_I))";
990 vbm
"errRateTerm :$((errRate*T_D))";
992 # Calculate PID control signal
993 ## ADJ = K_P * (errNS + errReset/T_I + errRate*T_D)
994 ADJ
="$(( K_P*(errNS + errReset/T_I + errRate*T_D) ))" && vbm
"ADJ:$ADJ";
995 # ADJ="$((K_P*(errNSx10e3 + (errResetx10e3/T_I) + (errRatex10e3*T_D) )/(10**3)))" && vbm "ADJ:$ADJ";
997 # Calculate BUFFER_TTL_ADJ_FLOAT from ADJ (ns)
998 ## Calculate BUFFER_TTL_ADJ in nanoseconds (BUFFER_TTL_ADJ_NS = BUFFER_TTL_NS + ADJ)
999 BUFFER_TTL_ADJ_NS
="$((BUFFER_TTL_NS + ADJ))" && vbm
"BUFFER_TTL_ADJ_NS:$BUFFER_TTL_ADJ_NS";
1000 ## Calculate integer seconds
1001 BUFFER_TTL_ADJ_INT
="$((BUFFER_TTL_ADJ_NS/(10**9)))" && vbm
"BUFFER_TTL_ADJ_INT:$BUFFER_TTL_ADJ_INT";
1002 ### Catch negative integer seconds, set minimum of BUFFER_TTL/10 seconds
1003 if [[ "$BUFFER_TTL_ADJ_INT" -le "$((BUFFER_TTL/10))" ]]; then
1004 BUFFER_TTL_ADJ_INT
="$((BUFFER_TTL/10))";
1005 yell
"WARNING:Buffer lag adjustment yielded negative seconds.";
1007 ## Calculate nanosecond remainder
1009 BUFFER_TTL_ADJ_FLOATFRAC
="$((BUFFER_TTL_ADJ_NS - (BUFFER_TTL_ADJ_INT*(10**9)) ))" && vbm
"BUFFER_TTL_ADJ_FLOATFRAC:$BUFFER_TTL_ADJ_FLOATFRAC";
1010 ### Calc absolute value of fraction (by removing '-' if present; see https://stackoverflow.com/a/47240327
1011 BUFFER_TTL_ADJ_FLOATFRAC
="${BUFFER_TTL_ADJ_FLOATFRAC#-}" && vbm
"BUFFER_TTL_ADJ_FLOATFRAC:$BUFFER_TTL_ADJ_FLOATFRAC";
1012 ## Form float BUFFER_TTL_ADJ_FLOAT
1013 BUFFER_TTL_ADJ_FLOAT
="$BUFFER_TTL_ADJ_INT".
"$BUFFER_TTL_ADJ_FLOATFRAC" && vbm
"BUFFER_TTL_ADJ_FLOAT:$BUFFER_TTL_ADJ_FLOAT";
1014 vbm
"STATUS:Calculated adjusted BUFFER_TTL (seconds):$BUFFER_TTL_ADJ_FLOAT";
1015 } # Calc BUFFER_TTL_ADJ_FLOAT so buffer starts every BUFFER_TTL seconds
1016 magicWriteVersion
() {
1017 # Desc: Appends time-stamped VERSION to PATHOUT_TAR
1018 # Usage: magicWriteVersion
1020 # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP
1021 # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME
1022 # Output: appends tar PATHOUT_TAR
1023 # Depends: dateTimeShort, appendArgTar
1024 local CONTENT_VERSION pubKeyIndex
1026 # Set VERSION file name
1027 FILEOUT_VERSION
="$(dateTimeShort)..VERSION";
1029 # Gather VERSION data in CONTENT_VERSION
1030 CONTENT_VERSION
="SCRIPT_VERSION=$SCRIPT_VERSION";
1031 #CONTENT_VERSION="$CONTENT_VERSION""\\n";
1032 CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME";
1033 CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL";
1034 CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION";
1035 CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL";
1036 CONTENT_VERSION
="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)";
1037 CONTENT_VERSION
="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME";
1038 ## Add list of recipient pubkeys
1039 for pubkey
in "${recPubKeysValid[@]}"; do
1041 CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1043 ## Process newline escapes
1044 CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")"
1046 # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR
1047 appendArgTar
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP";
1049 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar()
1050 magicGatherWriteBuffer
() {
1051 # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR
1052 # Inputs: vars: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP,
1053 # Inputs: vars: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX
1054 # Output: file: (PATHOUT_TAR)
1055 # Depends: yell(), try(), vbm(), appendArgTar(), tar 1, sleep 8, checkMakeTar()
1056 # Depends: magicWriteVersion(), appendFileTar()
1059 vbm
"DEBUG:STATUS:$FN:Started magicGatherWriteBuffer().";
1060 # Debug:Get function name
1061 FN
="${FUNCNAME[0]}";
1063 # Create buffer file with unique name
1064 PATHOUT_BUFFER
="$DIR_TMP/buffer$SECONDS" && vbm
"PATHOUT_BUFFER:$PATHOUT_BUFFER";
1066 timeout
"$BUFFER_TTL"s gpspipe
-r -o "$PATHOUT_BUFFER" ;
1067 timeBufferStart
="$(dateTimeShort "$
(date --date="$BUFFER_TTL seconds ago")" )" && vbm
"timeBufferStart:$timeBufferStart"; # Note start time
1068 # Determine file paths (time is start of buffer period)
1069 FILEOUT_BASENAME
="$timeBufferStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" && vbm
"STATUS:Set FILEOUT_BASENAME to:$FILEOUT_BASENAME";
1070 ## Files saved to DIR_TMP
1071 FILEOUT_NMEA
="$FILEOUT_BASENAME".nmea
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_NMEA to:$FILEOUT_NMEA";
1072 FILEOUT_GPX
="$FILEOUT_BASENAME".gpx
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_GPX to:$FILEOUT_GPX";
1073 FILEOUT_KML
="$FILEOUT_BASENAME".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 (DIR_OUT)
1078 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
1079 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort "$
(date --date="$BUFFER_TTL seconds ago")")"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
1080 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
1082 vbm
"STATUS:DIR_TMP :$DIR_TMP";
1083 vbm
"STATUS:PATHOUT_TAR :$PATHOUT_TAR";
1084 vbm
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA";
1085 vbm
"STATUS:PATHOUT_GPX:$PATHOUT_GPX";
1086 vbm
"STATUS:PATHOUT_KML:$PATHOUT_KML";
1089 # Validate PATHOUT_TAR as tar.
1090 checkMakeTar
"$PATHOUT_TAR";
1091 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
1092 if [[ $?
-eq 1 ]] ||
[[ $?
-eq 2 ]]; then magicWriteVersion
; fi
1094 # Write bufferBash to PATHOUT_TAR
1095 wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
1096 appendFileTar
"$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data
1097 appendFileTar
"$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file
1098 appendFileTar
"$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file
1100 # Remove secured chunks from DIR_TMP
1101 rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML";
1102 vbm
"DEBUG:STATUS:$FN:Finished magicGatherWriteBuffer().";
1103 } # write buffer to disk
1104 magicParseRecipientDir
() {
1105 # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory")
1106 # Inputs: vars: OPTION_RECDIR, argRecDir, OPTION_ENCRYPT
1107 # arry: recPubKeysValid
1108 # Outputs: arry: recPubKeysValid
1109 # Depends: processArguments,
1110 local recFileLine updateRecipients recipientDir
1111 declare -a candRecPubKeysValid
1113 # Check that '-e' and '-R' set
1114 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then
1115 ### Check that argRecDir is a directory.
1116 if [[ -d "$argRecDir" ]]; then
1117 recipientDir
="$argRecDir" && vbm
"STATUS:Recipient watch directory detected:\"$recipientDir\"";
1118 #### Initialize variable indicating outcome of pubkey review
1119 unset updateRecipients
1120 #### Add existing recipients
1121 candRecPubKeysValid
=("${recPubKeysValidStatic[@]}");
1122 #### Parse files in recipientDir
1123 for file in "$recipientDir"/*; do
1124 ##### Read first line of each file
1125 recFileLine
="$(head -n1 "$file")" && vbm
"STATUS:Checking if pubkey:\"$recFileLine\"";
1126 ##### check if first line is a valid pubkey
1127 if checkAgePubkey
"$recFileLine" && \
1128 ( validateInput
"$recFileLine" "ssh_pubkey" || validateInput
"$recFileLine" "age_pubkey"); then
1129 ###### T: add candidate pubkey to candRecPubKeysValid
1130 candRecPubKeysValid
+=("$recFileLine") && vbm
"STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\"";
1132 ###### F: throw warning;
1133 yell
"ERROR:Invalid recipient file detected. Not modifying recipient list."
1134 updateRecipients
="false";
1137 #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected
1138 if ! [[ "$updateRecipients" = "false" ]]; then
1139 recPubKeysValid
=("${candRecPubKeysValid[@]}") && vbm
"STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\"";
1142 yell
"ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
1145 # Handle case if '-R' set but '-e' not set
1146 if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then
1147 yell
"ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi;
1148 } # Update recPubKeysValid with argRecDir
1149 magicParseRecipientArgs
() {
1150 # Desc: Parses recipient arguments specified by '-r' option
1151 # Input: vars: OPTION_ENCRYPT from processArguments()
1152 # arry: argRecPubKeys from processArguments()
1153 # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX
1154 # arry: recPubKeysValid, recPubKeysValidStatic
1155 # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments()
1158 # Check if encryption option active.
1159 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then
1160 if checkapp age
; then # Check that age is available.
1161 for pubkey
in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
1162 vbm
"DEBUG:Testing pubkey string:$pubkey";
1163 if checkAgePubkey
"$pubkey" && \
1164 ( validateInput
"$pubkey" "ssh_pubkey" || validateInput
"$pubkey" "age_pubkey"); then
1165 #### Form age recipient string
1166 recipients
="$recipients""-r '$pubkey' ";
1167 vbm
"STATUS:Added pubkey for forming age recipient string:""$pubkey";
1168 vbm
"DEBUG:recipients:""$recipients";
1169 #### Add validated pubkey to recPubKeysValid array
1170 recPubKeysValid
+=("$pubkey") && vbm
"DEBUG:recPubkeysValid:pubkey added:$pubkey";
1172 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
1175 vbm
"DEBUG:Finished processing argRecPubKeys array";
1176 vbm
"STATUS:Array of validated pubkeys:${recPubKeysValid[*]}";
1177 recPubKeysValidStatic
=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function
1179 ## Form age command string
1180 CMD_ENCRYPT
="age ""$recipients " && vbm
"CMD_ENCRYPT:$CMD_ENCRYPT";
1181 CMD_ENCRYPT_SUFFIX
=".age" && vbm
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX";
1183 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
1186 CMD_ENCRYPT
="tee /dev/null " && vbm
"CMD_ENCRYPT:$CMD_ENCRYPT";
1187 CMD_ENCRYPT_SUFFIX
="" && vbm
"CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX";
1188 vbm
"DEBUG:Encryption not enabled."
1190 # Catch case if '-e' is set but '-r' or '-R' is not
1191 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECIPIENTS" = "true" ]]; then
1192 yell
"ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
1193 # Catch case if '-r' or '-R' set but '-e' is not
1194 if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then
1195 yell
"ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
1196 } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
1197 magicParseCompressionArg
() {
1198 # Desc: Parses compression arguments specified by '-c' option
1199 # Input: vars: OPTION_COMPRESS
1200 # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX
1201 # Depends: checkapp(), vbm(), gzip,
1202 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
1203 if checkapp
gzip; then # Check if gzip available
1204 CMD_COMPRESS
="gzip " && vbm
"CMD_COMPRESS:$CMD_COMPRESS";
1205 CMD_COMPRESS_SUFFIX
=".gz" && vbm
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX";
1207 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
1210 CMD_COMPRESS
="tee /dev/null " && vbm
"CMD_COMPRESS:$CMD_COMPRESS";
1211 CMD_COMPRESS_SUFFIX
="" && vbm
"CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX";
1212 vbm
"DEBUG:Compression not enabled.";
1214 } # Form compression cmd string and filename suffix
1215 magicInitWorkingDir
() {
1216 # Desc: Determine temporary working directory from defaults or user input
1217 # Usage: magicInitWorkignDir
1218 # Input: vars: OPTION_TEMPDIR, argTempDirPriority, DIR_TMP_DEFAULT
1219 # Input: vars: SCRIPT_TIME_START
1220 # Output: vars: DIR_TMP
1221 # Depends: processArguments(), vbm(), yell()
1222 # Parse '-t' option (user-specified temporary working dir)
1223 ## Set DIR_TMP_PARENT to user-specified value if specified
1224 local DIR_TMP_PARENT
1226 if [[ "$OPTION_TMPDIR" = "true" ]]; then
1227 if [[ -d "$argTempDirPriority" ]]; then
1228 DIR_TMP_PARENT
="$argTempDirPriority";
1230 yell
"WARNING:Specified temporary working directory not valid:$argTempDirPriority";
1231 exit 1; # Exit since user requires a specific temp dir and it is not available.
1234 ## Set DIR_TMP_PARENT to default or fallback otherwise
1235 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
1236 DIR_TMP_PARENT
="$DIR_TMP_DEFAULT";
1237 elif [[ -d /tmp
]]; then
1238 yell
"WARNING:$DIR_TMP_DEFAULT not available. Falling back to /tmp .";
1239 DIR_TMP_PARENT
="/tmp";
1241 yell
"ERROR:No valid working directory available. Exiting.";
1245 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
1246 DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().
1247 } # Sets working dir
1248 magicParseCustomTTL
() {
1249 # Desc: Set user-specified TTLs for buffer and script
1250 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
1251 # Input: vars: OPTION_CUSTOM_BUFFERTTL, OPTION_CUSTOM_SCRIPTTTL
1252 # Input: vars: BUFFER_TTL (integer), SCRIPT_TTL_TE (string)
1253 # Output: BUFFER_TTL (integer), SCRIPT_TTL_TE (string)
1254 # Depends validateInput(), showUsage(), yell
1256 # React to '-b, --buffer-ttl' option
1257 if [[ "$OPTION_CUSTOM_BUFFERTTL" = "true" ]]; then
1258 ## T: Check if argCustomBufferTTL is an integer
1259 if validateInput
"$argCustomBufferTTL" "integer"; then
1260 ### T: argCustomBufferTTL is an integer
1261 BUFFER_TTL
="$argCustomBufferTTL" && vbm
"Custom BUFFER_TTL from -b:$BUFFER_TTL";
1263 ### F: argcustomBufferTTL is not an integer
1264 yell
"ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage
; exit 1;
1266 ## F: do not change BUFFER_TTL
1269 # React to '-B, --script-ttl' option
1270 if [[ "$OPTION_CUSTOM_SCRIPTTTL_TE" = "true" ]]; then
1271 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
1272 if validateInput
"$argCustomScriptTTL" "time_element"; then
1273 ### T: argCustomScriptTTL is a time element
1274 SCRIPT_TTL_TE
="$argCustomScriptTTL" && vbm
"Custom SCRIPT_TTL_TE from -B:$SCRIPT_TTL_TE";
1276 ### F: argcustomScriptTTL is not a time element
1277 yell
"ERROR:Invalid time element argument for custom script time-to-live."; showUsage
; exit 1;
1279 ## F: do not change SCRIPT_TTL_TE
1281 } # Sets custom script or buffer TTL if specified
1286 processArguments
"$@";
1287 ## Act upon arguments
1288 ### Determine working directory
1289 magicInitWorkingDir
; # Sets DIR_TMP from argTempDirPriority
1290 ### Set output encryption and compression option strings
1291 #### React to "-r" ("encryption recipients") option
1292 magicParseRecipientArgs
; # Updates recPubKeysValid, CMD_ENCRYPT[_SUFFIX] from argRecPubKeys
1293 #### React to "-c" ("compression") option
1294 magicParseCompressionArg
; # Updates CMD_COMPRESS[_SUFFIX]
1295 #### React to "-R" ("recipient directory") option
1296 magicParseRecipientDir
; # Updates recPubKeysValid
1297 #### React to custom buffer and script TTL options ("-b", "-B")
1298 magicParseCustomTTL
; # Sets custom SCRIPT_TTL_TE and/or BUFFER_TTL if specified
1300 # Check that critical apps and dirs are available, display missing ones.
1301 if ! checkapp gpspipe
tar && ! checkdir
"$DIR_OUT" "DIR_TMP"; then
1302 yell
"ERROR:Critical components missing.";
1303 displayMissing
; yell
"Exiting."; exit 1; fi
1305 # Set script lifespan (SCRIPT_TTL from SCRIPT_TTL_TE)
1306 magicSetScriptTTL
"$SCRIPT_TTL_TE";
1307 ## Note: SCRIPT_TTL_TE is time element string (ex: "day") while SCRIPT_TTL is integer seconds
1309 # File name substring (ISO-8601 duration from BUFFER_TTL)
1310 bufferTTL_STR
="$(timeDuration "$BUFFER_TTL")";
1312 # Init temp working dir
1313 try mkdir
"$DIR_TMP" && vbm
"DEBUG:Working dir creatd at:$DIR_TMP";
1315 # Initialize 'tar' archive
1316 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
1317 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
1318 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
1319 ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise.
1320 checkMakeTar
"$PATHOUT_TAR" && vbm
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR";
1321 ## Append VERSION file to PATHOUT_TAR
1324 # Define GPS conversion commands
1325 CMD_CONV_NMEA
="tee /dev/null " && vbm
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough
1326 CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX
1327 CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML
1329 # MAIN LOOP:Record gps data until script lifespan ends
1330 timeBufferFirstNS
="$(timeEpochNS)"; bufferRound
=0; BUFFER_TTL_ADJ_FLOAT
="$BUFFER_TTL";
1331 while [[ "$SECONDS" -lt "$SCRIPT_TTL" ]]; do
1332 magicParseRecipientDir
;
1333 magicGatherWriteBuffer
&
1334 sleep "$BUFFER_TTL_ADJ_FLOAT"; # adjusted by magicBufferSleepPID
1336 magicBufferSleepPID
; # Calculates BUFFER_TTL_ADJ_FLOAT from BUFFER_TTL given buffer expected start time vs. actual
1341 try
rm -r "$DIR_TMP";
1343 vbm
"STATUS:Main function finished.";
1345 #===END Declare local script functions===
1346 #==END Define script parameters==
1349 #==BEGIN Perform work and exit==
1350 main
"$@" # Run main function.
1352 #==END Perform work and exit==