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
="60"; # time between file writes
10 SCRIPT_TTL
="day"; # (day|hour)
11 #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option
12 DIR_TMP_DEFAULT
="/dev/shm"; # Default parent of working directory
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.3.1"; # 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 recPubKeys
# for processArguments function
27 declare recipients
# for main function
29 ## Initialize variables
30 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
="";
32 #===BEGIN Declare local script functions===
34 # Desc: If arg is a command, save result in assoc array 'appRollCall'
35 # Usage: checkapp arg1 arg2 arg3 ...
36 # Input: global assoc. array 'appRollCall'
37 # Output: adds/updates key(value) to global assoc array 'appRollCall'
39 #echo "DEBUG:$(date +%S.%N)..Starting checkapp function."
40 #echo "DEBUG:args: $@"
41 #echo "DEBUG:returnState:$returnState"
45 #echo "DEBUG:processing arg:$arg"
46 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
47 appRollCall
[$arg]="true";
48 #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]}
49 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
51 appRollCall
[$arg]="false"; returnState
="false";
55 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
56 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
58 #===Determine function return code===
59 if [ "$returnState" = "true" ]; then
60 #echo "DEBUG:checkapp returns true for $arg";
63 #echo "DEBUG:checkapp returns false for $arg";
66 } # Check that app exists
68 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
69 # Usage: checkfile arg1 arg2 arg3 ...
70 # Input: global assoc. array 'fileRollCall'
71 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
72 # Output: returns 0 if app found, 1 otherwise
77 #echo "DEBUG:processing arg:$arg"
78 if [ -f "$arg" ]; then
79 fileRollCall
["$arg"]="true";
80 #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]}
81 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
83 fileRollCall
["$arg"]="false"; returnState
="false";
87 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done
88 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
90 #===Determine function return code===
91 if [ "$returnState" = "true" ]; then
92 #echo "DEBUG:checkapp returns true for $arg";
95 #echo "DEBUG:checkapp returns false for $arg";
98 } # Check that file exists
100 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
101 # Usage: checkdir arg1 arg2 arg3 ...
102 # Input: global assoc. array 'dirRollCall'
103 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
104 # Output: returns 0 if app found, 1 otherwise
109 #echo "DEBUG:processing arg:$arg"
110 if [ -d "$arg" ]; then
111 dirRollCall
["$arg"]="true";
112 #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]}
113 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
114 elif [ "$arg" = "" ]; then
115 dirRollCall
["$arg"]="false"; returnState
="false";
121 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done
122 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
124 #===Determine function return code===
125 if [ "$returnState" = "true" ]; then
126 #echo "DEBUG:checkapp returns true for $arg";
129 #echo "DEBUG:checkapp returns false for $arg";
132 } # Check that dir exists
134 # Yell, Die, Try Three-Fingered Claw technique
135 # Ref/Attrib: https://stackoverflow.com/a/25515370
136 yell
() { echo "$0: $*" >&2; }
137 die
() { yell
"$*"; exit 111; }
138 try
() { "$@" || die
"cannot $*"; }
141 echo "$@" 1>&2; # Define stderr echo function.
142 } # Define stderr message function.
145 echoerr
" bkgpslog [ options ]"
148 echoerr
" -h, --help"
149 echoerr
" Display help information."
152 echoerr
" Display script version."
154 echoerr
" -v, --verbose"
155 echoerr
" Display debugging info."
157 echoerr
" -e, --encrypt"
158 echoerr
" Encrypt output."
160 echoerr
" -r, --recipient [ pubkey string ]"
161 echoerr
" Specify recipient. May be age or ssh pubkey."
162 echoerr
" See https://github.com/FiloSottile/age"
164 echoerr
" -o, --output [ directory ]"
165 echoerr
" Specify output directory to save logs."
167 echoerr
" -c, --compress"
168 echoerr
" Compress output with gzip (before encryption if enabled)."
170 echoerr
" -z, --time-zone"
171 echoerr
" Specify time zone. (ex: \"America/New_York\")"
173 echoerr
" -t, --temp-dir"
174 echoerr
" Specify parent directory for temporary working directory."
175 echoerr
" Default: \"/dev/shm\""
177 echoerr
"EXAMPLE: (bash script lines)"
178 echoerr
"/bin/bash bkgpslog -e -c \\"
179 echoerr
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
180 echoerr
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
181 echoerr
"-o ~/Sync/Location"
182 } # Display information on how to use this script.
184 echoerr
"$SCRIPT_VERSION"
185 } # Display script version.
187 # Usage: vbm "DEBUG:verbose message here"
188 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
190 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
191 # - "$@" positional arguments fed to this function.
193 # Script function dependencies: echoerr
194 # External function dependencies: echo
195 # Last modified: 2020-04-11T23:57Z
196 # Last modified by: Steven Baltakatei Sandoval
200 if [ "$OPTION_VERBOSE" = "true" ]; then
201 FUNCTION_TIME
=$
(date --iso-8601=ns
); # Save current time in nano seconds.
202 echoerr
"[$FUNCTION_TIME] ""$*"; # Display argument text.
206 return 0; # Function finished.
207 } # Verbose message display function.
209 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
210 #echoerr "DEBUG:Starting processArguments while loop."
211 #echoerr "DEBUG:Provided arguments are:""$*"
213 -h |
--help) showUsage
; exit 1;; # Display usage.
214 --version) showVersion
; exit 1;; # Show version
215 -v |
--verbose) OPTION_VERBOSE
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode.
216 -o |
--output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory.
217 -e |
--encrypt) OPTION_ENCRYPT
="true"; vbm
"DEBUG:Encrypted output mode enabled.";;
218 -r |
--recipient) # Add 'age' recipient via public key string
219 recPubKeys
+=("$2"); vbm
"STATUS:pubkey added:""$2"; shift;;
220 -c |
--compress) OPTION_COMPRESS
="true"; vbm
"DEBUG:Compressed output mode enabled.";;
221 -z |
--time-zone) try setTimeZoneEV
"$2"; shift;;
222 -t |
--temp-dir) OPTION_TMPDIR
="true" && TMP_DIR_PRIORITY
="$2"; shift;;
223 *) echoerr
"ERROR: Unrecognized argument: $1"; echoerr
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
227 } # Argument Processing
229 # Desc: Set time zone environment variable TZ
230 # Usage: setTimeZoneEV arg1
231 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
232 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
234 # exit code 0 on success
235 # exit code 1 on incorrect number of arguments
236 # exit code 2 if unable to validate arg1
237 # Depends: yell, printenv, bash 5
238 # Tested on: Debian 10
240 local tzDir returnState
241 if ! [[ $# -eq 1 ]]; then
242 yell
"ERROR:Invalid argument count.";
246 # Read TZDIR env var if available
247 if printenv TZDIR
1>/dev
/null
2>&1; then
248 tzDir
="$(printenv TZDIR)";
250 tzDir
="/usr/share/zoneinfo";
254 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
255 yell
"ERROR:Invalid time zone argument.";
258 # Export ARG1 as TZ environment variable
259 TZ
="$ARG1" && export TZ
&& returnState
="true";
262 # Determine function return code
263 if [ "$returnState" = "true" ]; then
266 } # Exports TZ environment variable
268 # Desc: Report seconds until next day.
270 # Output: stdout: integer seconds until next day
271 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
272 # Usage: timeUntilNextDay
273 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
274 # Depends: date 8, echo 8, yell, try
276 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
278 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
279 TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
280 SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
281 if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then
283 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then
284 returnState
="warning_zero";
285 yell
"WARNING:Reported time until next day exactly zero.";
286 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then
287 returnState
="warning_negative";
288 yell
"WARNING:Reported time until next day is negative.";
291 try
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
293 # Determine function return code
294 if [[ "$returnState" = "true" ]]; then
296 elif [[ "$returnState" = "warning_zero" ]]; then
298 elif [[ "$returnState" = "warning_negative" ]]; then
301 } # Report seconds until next day
303 # Desc: Report seconds until next hour
305 # Output: stdout: integer seconds until next hour
306 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
307 # Usage: timeUntilNextHour
308 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
310 local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
311 TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
312 TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
313 SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second).
314 if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then
316 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then
317 returnState
="warning_zero";
318 yell
"WARNING:Reported time until next hour exactly zero.";
319 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then
320 returnState
="warning_negative";
321 yell
"WARNING:Reported time until next hour is negative.";
324 try
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
326 # Determine function return code
327 if [[ "$returnState" = "true" ]]; then
329 elif [[ "$returnState" = "warning_zero" ]]; then
331 elif [[ "$returnState" = "warning_negative" ]]; then
334 } # Report seconds until next hour
336 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
337 # Usage: dateTimeShort
338 # Output: stdout: timestamp (ISO-8601, no separators)
339 local TIME_CURRENT TIME_CURRENT_SHORT
340 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
341 TIME_CURRENT_SHORT
="$(date -d "$TIME_CURRENT" +%Y%m%dT%H%M%S%z)"; # Produce separator-less current timestamp with resolution 1 second.
342 echo "$TIME_CURRENT_SHORT";
343 } # Get YYYYmmddTHHMMSS±zzzz
345 # Desc: Date without separators (YYYYmmdd)
347 # Output: stdout: date (ISO-8601, no separators)
348 local TIME_CURRENT DATE_CURRENT_SHORT
349 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
350 DATE_CURRENT_SHORT
="$(date -d "$TIME_CURRENT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
351 echo "$DATE_CURRENT_SHORT";
354 # Desc: Given seconds, output ISO-8601 duration string
355 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
356 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
357 # Usage: timeDuration [1:seconds] ([2:precision])
359 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
360 # arg2: precision level (optional; default=2)
361 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
362 # exit code 0: success
363 # exit code 1: error_input
364 # exit code 2: error_unknown
365 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
366 # Depends: date 8 (gnucoreutils), yell,
367 local returnState argSeconds argPrecision remainder precision witherPrecision
368 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
369 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
370 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
372 argSeconds
="$1"; # read arg1 (seconds)
373 argPrecision
="$2"; # read arg2 (precision)
374 precision
=2; # set default precision
376 # Check that between one and two arguments is supplied
377 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
378 yell
"ERROR:Invalid number of arguments:$# . Exiting.";
379 returnState
="error_input"; fi
381 # Check that argSeconds provided
382 if [[ $# -ge 1 ]]; then
383 ## Check that argSeconds is a positive integer
384 if [[ "$argSeconds" =~ ^
[[:digit
:]]+$
]]; then
387 yell
"ERROR:argSeconds not a digit.";
388 returnState
="error_input";
391 yell
"ERROR:No argument provided. Exiting.";
395 # Consider whether argPrecision was provided
396 if [[ $# -eq 2 ]]; then
397 # Check that argPrecision is a positive integer
398 if [[ "$argPrecision" =~ ^
[[:digit
:]]+$
]] && [[ "$argPrecision" -gt 0 ]]; then
399 precision
="$argPrecision";
401 yell
"ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
402 returnState
="error_input";
408 remainder
="$argSeconds" ; # seconds
409 ## Calculate full years Y, update remainder
410 fullYears
=$
(( remainder
/ (365*24*60*60) ));
411 remainder
=$
(( remainder
- (fullYears
*365*24*60*60) ));
412 ## Calculate full months M, update remainder
413 fullMonths
=$
(( remainder
/ (30*24*60*60) ));
414 remainder
=$
(( remainder
- (fullMonths
*30*24*60*60) ));
415 ## Calculate full days D, update remainder
416 fullDays
=$
(( remainder
/ (24*60*60) ));
417 remainder
=$
(( remainder
- (fullDays
*24*60*60) ));
418 ## Calculate full hours H, update remainder
419 fullHours
=$
(( remainder
/ (60*60) ));
420 remainder
=$
(( remainder
- (fullHours
*60*60) ));
421 ## Calculate full minutes M, update remainder
422 fullMinutes
=$
(( remainder
/ (60) ));
423 remainder
=$
(( remainder
- (fullMinutes
*60) ));
424 ## Calculate full seconds S, update remainder
425 fullSeconds
=$
(( remainder
/ (1) ));
426 remainder
=$
(( remainder
- (remainder
*1) ));
427 ## Check which fields filled
428 if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi
429 if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi
430 if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi
431 if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi
432 if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi
433 if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi
435 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
436 witherPrecision
="false"
439 if $hasYears && [[ $precision -gt 0 ]]; then
441 witherPrecision
="true";
443 displayYears
="false";
445 if $witherPrecision; then ((precision--
)); fi;
448 if $hasMonths && [[ $precision -gt 0 ]]; then
449 displayMonths
="true";
450 witherPrecision
="true";
452 displayMonths
="false";
454 if $witherPrecision && [[ $precision -gt 0 ]]; then
455 displayMonths
="true";
457 if $witherPrecision; then ((precision--
)); fi;
460 if $hasDays && [[ $precision -gt 0 ]]; then
462 witherPrecision
="true";
466 if $witherPrecision && [[ $precision -gt 0 ]]; then
469 if $witherPrecision; then ((precision--
)); fi;
472 if $hasHours && [[ $precision -gt 0 ]]; then
474 witherPrecision
="true";
476 displayHours
="false";
478 if $witherPrecision && [[ $precision -gt 0 ]]; then
481 if $witherPrecision; then ((precision--
)); fi;
484 if $hasMinutes && [[ $precision -gt 0 ]]; then
485 displayMinutes
="true";
486 witherPrecision
="true";
488 displayMinutes
="false";
490 if $witherPrecision && [[ $precision -gt 0 ]]; then
491 displayMinutes
="true";
493 if $witherPrecision; then ((precision--
)); fi;
497 if $hasSeconds && [[ $precision -gt 0 ]]; then
498 displaySeconds
="true";
499 witherPrecision
="true";
501 displaySeconds
="false";
503 if $witherPrecision && [[ $precision -gt 0 ]]; then
504 displaySeconds
="true";
506 if $witherPrecision; then ((precision--
)); fi;
508 ## Determine whether or not the "T" separator is needed to separate date and time elements
509 if ( $displayHours ||
$displayMinutes ||
$displaySeconds); then
510 displayDateTime
="true"; else displayDateTime
="false"; fi
512 ## Construct duration output string
514 if $displayYears; then
515 OUTPUT
=$OUTPUT$fullYears"Y"; fi
516 if $displayMonths; then
517 OUTPUT
=$OUTPUT$fullMonths"M"; fi
518 if $displayDays; then
519 OUTPUT
=$OUTPUT$fullDays"D"; fi
520 if $displayDateTime; then
521 OUTPUT
=$OUTPUT"T"; fi
522 if $displayHours; then
523 OUTPUT
=$OUTPUT$fullHours"H"; fi
524 if $displayMinutes; then
525 OUTPUT
=$OUTPUT$fullMinutes"M"; fi
526 if $displaySeconds; then
527 OUTPUT
=$OUTPUT$fullSeconds"S"; fi
529 ## Output duration string to stdout
530 echo "$OUTPUT" && returnState
="true";
532 #===Determine function return code===
533 if [ "$returnState" = "true" ]; then
535 elif [ "$returnState" = "error_input" ]; then
539 yell
"ERROR:Unknown";
543 } # Get duration (ex: PT10M4S )
545 # Desc: Displays missing apps, files, and dirs
546 # Usage: displayMissing
547 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
548 # Output: stderr messages
549 #==BEGIN Display errors==
550 #===BEGIN Display Missing Apps===
551 missingApps
="Missing apps :"
552 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
553 for key
in "${!appRollCall[@]}"; do
554 value
="${appRollCall[$key]}"
555 if [ "$value" = "false" ]; then
556 #echo "DEBUG:Missing apps: $key => $value";
557 missingApps
="$missingApps""$key "
561 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
562 echo "$missingApps" 1>&2;
564 #===END Display Missing Apps===
566 #===BEGIN Display Missing Files===
567 missingFiles
="Missing files:"
568 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
569 for key
in "${!fileRollCall[@]}"; do
570 value
="${fileRollCall[$key]}"
571 if [ "$value" = "false" ]; then
572 #echo "DEBUG:Missing files: $key => $value";
573 missingFiles
="$missingFiles""$key "
577 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
578 echo "$missingFiles" 1>&2;
580 #===END Display Missing Files===
582 #===BEGIN Display Missing Directories===
583 missingDirs
="Missing dirs:"
584 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
585 for key
in "${!dirRollCall[@]}"; do
586 value
="${dirRollCall[$key]}"
587 if [ "$value" = "false" ]; then
588 #echo "DEBUG:Missing dirs: $key => $value";
589 missingDirs
="$missingDirs""$key "
593 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
594 echo "$missingDirs" 1>&2;
596 #===END Display Missing Directories===
598 #==END Display errors==
599 } # Display missing apps, files, dirs
601 #Desc: Sets script TTL
602 #Usage: setScriptTTL arg1
603 #Input: arg1: "day" or "hour"
605 #Depends: timeUntilNextHour or timeUntilNextDay
608 if [[ "$ARG1" = "day" ]]; then
609 # Set script lifespan to end at start of next day
610 if ! scriptTTL
="$(timeUntilNextDay)"; then
611 if [[ "$scriptTTL" -eq 0 ]]; then
612 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
614 yell
"ERROR: timeUntilNextDay exit code $?"; exit 1;
617 elif [[ "$ARG1" = "hour" ]]; then
618 # Set script lifespan to end at start of next hour
619 if ! scriptTTL
="$(timeUntilNextHour)"; then
620 if [[ "$scriptTTL" -eq 0 ]]; then
621 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
623 yell
"ERROR: timeUntilNextHour exit code $?"; exit 1;
627 yell
"ERROR:Invalid argument for setScriptTTL function."; exit 1;
629 } # Seconds until next (day|hour).
631 # Desc: Checks that a valid tar archive exists, creates one otherwise
632 # Usage: checkMakeTar [ path ]
634 # Input: arg1: path of tar archive
635 # Output: exit code 0 : tar readable
636 # exit code 1 : tar missing; created
637 # exit code 2 : tar not readable; moved; replaced
638 # Depends: try, tar, date
639 local PATH_TAR returnFlag0 returnFlag1 returnFlag2
642 # Check if file is a valid tar archive
643 if tar --list --file="$PATH_TAR" 1>/dev
/null
2>&1; then
644 ## T1: return success
645 returnFlag0
="tar valid";
647 ## F1: Check if file exists
648 if [[ -f "$PATH_TAR" ]]; then
650 try
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
651 returnFlag1
="tar moved";
656 ## F2: Create tar archive, return 0
657 try
tar --create --file="$PATH_TAR" --files-from=/dev
/null
&& \
658 returnFlag2
="tar created";
661 # Determine function return code
662 if [[ "$returnFlag0" = "tar valid" ]]; then
664 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
665 return 1; # tar missing so created
666 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
667 return 2; # tar not readable so moved; replaced
669 } # checks if arg1 is tar; creates one otherwise
671 # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar
672 # Usage: writeArg "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
674 # Input: arg1: data to be written
675 # arg2: file name of file to be inserted into tar
676 # arg3: tar archive path (must exist first)
677 # arg4: temporary working dir
678 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
679 # Output: file written to disk
680 # Example: decrypt multiple large files in parallel
681 # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
682 # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
683 # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
687 local FN
="${FUNCNAME[0]}";
688 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
691 if ! [ -z "$2" ]; then FILENAME
="$2"; else yell
"ERROR:$FN:Not enough arguments."; exit 1; fi
693 # Check tar path is a file
694 if [ -f "$3" ]; then TAR_PATH
="$3"; else yell
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi
697 if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell
"ERROR:$FN:No temporary working dir set."; exit 1; fi
699 # Set command strings
700 if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1
701 if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2
702 if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3
703 if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4
706 # yell "DEBUG:STATUS:$FN:CMD1:$CMD1"
707 # yell "DEBUG:STATUS:$FN:CMD2:$CMD2"
708 # yell "DEBUG:STATUS:$FN:CMD3:$CMD3"
709 # yell "DEBUG:STATUS:$FN:CMD4:$CMD4"
710 # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME"
711 # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH"
712 # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR"
714 # Write to temporary working dir
715 echo "$1" |
$CMD1 |
$CMD2 |
$CMD3 |
$CMD4 > "$TMP_DIR"/"$FILENAME";
718 try
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
719 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
720 } # Append Bash var to file appended to Tar archive
721 magicWriteVersion
() {
722 # Desc: Appends time-stamped VERSION to PATHOUT_TAR
723 # Usage: magicWriteVersion
725 # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP
726 # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME
727 # Output: appends tar PATHOUT_TAR
728 # Depends: dateTimeShort, appendArgTar
729 local CONTENT_VERSION pubKeyIndex
731 # Set VERSION file name
732 FILEOUT_VERSION
="$(dateTimeShort)..VERSION";
734 # Gather VERSION data in CONTENT_VERSION
735 CONTENT_VERSION
="SCRIPT_VERSION=$SCRIPT_VERSION";
736 #CONTENT_VERSION="$CONTENT_VERSION""\\n";
737 CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME";
738 CONTENT_VERSION
="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL";
739 CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION";
740 CONTENT_VERSION
="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL";
741 CONTENT_VERSION
="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)";
742 CONTENT_VERSION
="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME";
743 ## Add list of recipient pubkeys
744 for pubkey
in "${recPubKeysValid[@]}"; do
746 CONTENT_VERSION
="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey";
748 ## Process newline escapes
749 CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")"
751 # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR
752 appendArgTar
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP";
754 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar()
756 # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR
757 # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP
758 # Depends: yell, try, vbm, appendArgTar, tar
759 local FN
="${FUNCNAME[0]}";
760 wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
761 vbm
"DEBUG:STATUS:$FN:Started magicWriteBuffer().";
763 # Validate PATHOUT_TAR as tar.
764 checkMakeTar
"$PATHOUT_TAR";
765 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
766 if [[ $?
-eq 1 ]] ||
[[ $?
-eq 2 ]]; then magicWriteVersion
; fi
768 # Write bufferBash to PATHOUT_TAR
769 appendArgTar
"$bufferBash" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data
770 appendArgTar
"$bufferBash" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file
771 appendArgTar
"$bufferBash" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file
772 # Remove secured chunks from DIR_TMP
773 try
rm "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML";
774 # yell "DEBUG:STATUS:$FN:Finished magicWriteBuffer().";
775 } # bkgpslog write function
778 processArguments
"$@" # Process arguments.
780 # Determine working directory
781 ## Set DIR_TMP_PARENT to user-specified value if specified
782 if [[ "$OPTION_TMPDIR" = "true" ]]; then
783 if [[ -d "$TMP_DIR_PRIORITY" ]]; then
784 DIR_TMP_PARENT
="$OPTION_TMPDIR";
786 yell
"WARNING:Specified temporary working directory not valid:$OPTION_TMPDIR";
791 ## Set DIR_TMP_PARENT to default or fallback otherwise
792 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
793 DIR_TMP_PARENT
="$DIR_TMP_DEFAULT";
794 elif [[ -d /tmp
]]; then
795 yell
"WARNING:/dev/shm not available. Falling back to /tmp .";
796 DIR_TMP_PARENT
="/tmp";
798 yell
"ERROR:No valid working directory available. Exiting.";
802 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
803 DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().
805 # Set output encryption and compression option strings
806 if [[ "$OPTION_ENCRYPT" = "true" ]]; then # Check if encryption option active.
807 if checkapp age
; then # Check that age is available.
808 for pubkey
in "${recPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
809 vbm
"DEBUG:Testing pubkey string:$pubkey"
810 if echo "butts" | age
-a -r "$pubkey" 1>/dev
/null
; then
811 #### Form age recipient string
812 recipients
="$recipients""-r $pubkey ";
813 vbm
"STATUS:Added pubkey for forming age recipient string:""$pubkey";
814 vbm
"DEBUG:recipients:""$recipients";
815 #### Add validated pubkey to recPubKeysValid array
816 recPubKeysValid
+=("$pubkey") && vbm
"DEBUG:recPubkeysValid:pubkey added:$pubkey";
818 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
821 vbm
"DEBUG:Finished processing recPubKeys array";
823 ## Form age command string
824 CMD_ENCRYPT
="age ""$recipients ";
825 CMD_ENCRYPT_SUFFIX
=".age";
827 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
830 CMD_ENCRYPT
="tee /dev/null ";
831 CMD_ENCRYPT_SUFFIX
="";
832 vbm
"DEBUG:Encryption not enabled."
834 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
835 if checkapp
gzip; then # Check if gzip available
836 CMD_COMPRESS
="gzip ";
837 CMD_COMPRESS_SUFFIX
=".gz";
839 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
842 CMD_COMPRESS
="tee /dev/null ";
843 CMD_COMPRESS_SUFFIX
="";
844 vbm
"DEBUG:Compression not enabled.";
847 # Check that critical apps and dirs are available, displag missing ones.
848 if ! checkapp gpspipe
tar && ! checkdir
"$DIR_OUT" "/dev/shm"; then
849 yell
"ERROR:Critical components missing.";
850 displayMissing
; yell
"Exiting."; exit 1; fi
852 # Set script lifespan
853 setScriptTTL
"$SCRIPT_TTL"; # seconds until next new SCRIPT_TTL (ex: "day" or "hour")
855 # File name substring: encoded bufferTTL
856 bufferTTL_STR
="$(timeDuration $BUFFER_TTL)";
858 # Init temp working dir
859 try mkdir
"$DIR_TMP" && vbm
"DEBUG:Working dir creatd at:$DIR_TMP";
861 # Initialize 'tar' archive
862 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
863 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
864 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
865 ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise.
866 checkMakeTar
"$PATHOUT_TAR" && vbm
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR";
867 ## Append VERSION file to PATHOUT_TAR
870 # Record gps data until script lifespan ends
871 declare debugCounter
; debugCounter
="0"; # set debug counter
872 while [[ "$SECONDS" -lt "$scriptTTL" ]]; do
873 timeBufferStart
="$(dateTimeShort)"; # Note start time
874 # Determine file paths (time is start of buffer period)
875 FILEOUT_BASENAME
="$timeBufferStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" && vbm
"STATUS:Set FILEOUT_BASENAME to:$FILEOUT_BASENAME";
876 ## Files saved to DIR_TMP
877 FILEOUT_NMEA
="$FILEOUT_BASENAME".nmea
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_NMEA to:$FILEOUT_NMEA";
878 FILEOUT_GPX
="$FILEOUT_BASENAME".gpx
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_GPX to:$FILEOUT_GPX";
879 FILEOUT_KML
="$FILEOUT_BASENAME".kml
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_KML to:$FILEOUT_KML";
880 PATHOUT_NMEA
="$DIR_TMP"/"$FILEOUT_NMEA" && vbm
"STATUS:Set PATHOUT_NMEA to:$PATHOUT_NMEA";
881 PATHOUT_GPX
="$DIR_TMP"/"$FILEOUT_GPX" && vbm
"STATUS:Set PATHOUT_GPX to:$PATHOUT_GPX";
882 PATHOUT_KML
="$DIR_TMP"/"$FILEOUT_KML" && vbm
"STATUS:Set PATHOUT_KML to:$PATHOUT_KML";
883 ## Files saved to disk (DIR_OUT)
884 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
885 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
886 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
887 # Define GPS conversion commands
888 CMD_CONV_NMEA
="tee /dev/null " && vbm
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough
889 CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX
890 CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML
891 # Fill Bash variable buffer
892 bufferBash
="$(timeout "$BUFFER_TTL""s
" gpspipe -r)" && vbm
"STATUS:Successfully filled bufferBash variable with gpspipe data."; # Record gpspipe nmea data to buffer for bufferTTL seconds
893 # Process bufferBash, save secured chunk set to DIR_TMP
894 vbm
"STATUS:Beginning to save data to $DIR_TMP";
896 # Append each secured chunk in memory dir (DIR_TMP) to file on disk (PATHOUT_TAR in DIR_OUT)
897 vbm
"STATUS:DIR_TMP :$DIR_TMP";
898 vbm
"STATUS:PATHOUT_TAR :$PATHOUT_TAR";
899 vbm
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA";
900 vbm
"STATUS:PATHOUT_GPX:$PATHOUT_GPX";
901 vbm
"STATUS:PATHOUT_KML:$PATHOUT_KML";
903 # Reset buffer and filenames
904 unset bufferBash FILEOUT_BASENAME PATHOUT_NMEA PATHOUT_GPX PATHOUT_KML PATHOUT_TAR timeBufferStart
;
905 vbm
"DEBUG:Completed buffer session $debugCounter ." 1>&2;
910 try
rm -r "$DIR_TMP";
912 vbm
"STATUS:Main function finished.";
914 #===END Declare local script functions===
915 #==END Define script parameters==
918 #==BEGIN Perform work and exit==
919 main
"$@" # Run main function.
921 #==END Perform work and exit==