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
="bkgpslog 0.2.1"; # Define version of script.
18 SCRIPT_NAME
="$(basename "$0")"; # Define basename of script file.
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 recPubKeys
# for processArguments function
24 declare recipients
# for main function
26 ## Initialize variables
27 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
="";
29 #===BEGIN Declare local script functions===
31 # Desc: If arg is a command, save result in assoc array 'appRollCall'
32 # Usage: checkapp arg1 arg2 arg3 ...
33 # Input: global assoc. array 'appRollCall'
34 # Output: adds/updates key(value) to global assoc array 'appRollCall'
36 #echo "DEBUG:$(date +%S.%N)..Starting checkapp function."
37 #echo "DEBUG:args: $@"
38 #echo "DEBUG:returnState:$returnState"
42 #echo "DEBUG:processing arg:$arg"
43 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
44 appRollCall
[$arg]="true";
45 #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]}
46 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
48 appRollCall
[$arg]="false"; returnState
="false";
52 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
53 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
55 #===Determine function return code===
56 if [ "$returnState" = "true" ]; then
57 #echo "DEBUG:checkapp returns true for $arg";
60 #echo "DEBUG:checkapp returns false for $arg";
63 } # Check that app exists
65 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
66 # Usage: checkfile arg1 arg2 arg3 ...
67 # Input: global assoc. array 'fileRollCall'
68 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
69 # Output: returns 0 if app found, 1 otherwise
74 #echo "DEBUG:processing arg:$arg"
75 if [ -f "$arg" ]; then
76 fileRollCall
["$arg"]="true";
77 #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]}
78 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
80 fileRollCall
["$arg"]="false"; returnState
="false";
84 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done
85 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
87 #===Determine function return code===
88 if [ "$returnState" = "true" ]; then
89 #echo "DEBUG:checkapp returns true for $arg";
92 #echo "DEBUG:checkapp returns false for $arg";
95 } # Check that file exists
97 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
98 # Usage: checkdir arg1 arg2 arg3 ...
99 # Input: global assoc. array 'dirRollCall'
100 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
101 # Output: returns 0 if app found, 1 otherwise
106 #echo "DEBUG:processing arg:$arg"
107 if [ -d "$arg" ]; then
108 dirRollCall
["$arg"]="true";
109 #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]}
110 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
111 elif [ "$arg" = "" ]; then
112 dirRollCall
["$arg"]="false"; returnState
="false";
118 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done
119 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
121 #===Determine function return code===
122 if [ "$returnState" = "true" ]; then
123 #echo "DEBUG:checkapp returns true for $arg";
126 #echo "DEBUG:checkapp returns false for $arg";
129 } # Check that dir exists
131 # Yell, Die, Try Three-Fingered Claw technique
132 # Ref/Attrib: https://stackoverflow.com/a/25515370
133 yell
() { echo "$0: $*" >&2; }
134 die
() { yell
"$*"; exit 111; }
135 try
() { "$@" || die
"cannot $*"; }
138 echo "$@" 1>&2; # Define stderr echo function.
139 } # Define stderr message function.
142 echoerr
" bkgpslog [ options ]"
145 echoerr
" -h, --help"
146 echoerr
" Display help information."
149 echoerr
" Display script version."
151 echoerr
" -v, --verbose"
152 echoerr
" Display debugging info."
154 echoerr
" -e, --encrypt"
155 echoerr
" Encrypt output."
157 echoerr
" -r, --recipient [ pubkey string ]"
158 echoerr
" Specify recipient. May be age or ssh pubkey."
159 echoerr
" See https://github.com/FiloSottile/age"
161 echoerr
" -o, --output [ directory ]"
162 echoerr
" Specify output directory to save logs."
164 echoerr
" -c, --compress"
165 echoerr
" Compress output with gzip (before encryption if enabled)."
167 echoerr
" -z, --time-zone"
168 echoerr
" Specify time zone. (ex: \"America/New_York\")"
170 echoerr
" -t, --temp-dir"
171 echoerr
" Specify parent directory for temporary working directory."
172 echoerr
" Default: \"/dev/shm\""
174 echoerr
"EXAMPLE: (bash script lines)"
175 echoerr
"/bin/bash bkgpslog -e -c \\"
176 echoerr
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
177 echoerr
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
178 echoerr
"-o ~/Sync/Location"
179 } # Display information on how to use this script.
181 echoerr
"$SCRIPT_VERSION"
182 } # Display script version.
184 # Usage: vbm "DEBUG:verbose message here"
185 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
187 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
188 # - "$@" positional arguments fed to this function.
190 # Script function dependencies: echoerr
191 # External function dependencies: echo
192 # Last modified: 2020-04-11T23:57Z
193 # Last modified by: Steven Baltakatei Sandoval
197 if [ "$OPTION_VERBOSE" = "true" ]; then
198 FUNCTION_TIME
=$
(date --iso-8601=ns
); # Save current time in nano seconds.
199 echoerr
"[$FUNCTION_TIME] ""$*"; # Display argument text.
203 return 0; # Function finished.
204 } # Verbose message display function.
206 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
207 #echoerr "DEBUG:Starting processArguments while loop."
208 #echoerr "DEBUG:Provided arguments are:""$*"
210 -h |
--help) showUsage
; exit 1;; # Display usage.
211 --version) showVersion
; exit 1;; # Show version
212 -v |
--verbose) OPTION_VERBOSE
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode.
213 -o |
--output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory.
214 -e |
--encrypt) OPTION_ENCRYPT
="true"; vbm
"DEBUG:Encrypted output mode enabled.";;
215 -r |
--recipient) # Add 'age' recipient via public key string
216 recPubKeys
+=("$2"); vbm
"STATUS:pubkey added:""$2"; shift;;
217 -c |
--compress) OPTION_COMPRESS
="true"; vbm
"DEBUG:Compressed output mode enabled.";;
218 -z |
--time-zone) try setTimeZoneEV
"$2"; shift;;
219 -t |
--temp-dir) OPTION_TMPDIR
="true" && TMP_DIR_PRIORITY
="$2"; shift;;
220 *) echoerr
"ERROR: Unrecognized argument: $1"; echoerr
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
224 } # Argument Processing
226 # Desc: Set time zone environment variable TZ
227 # Usage: setTimeZoneEV arg1
228 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
229 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
231 # exit code 0 on success
232 # exit code 1 on incorrect number of arguments
233 # exit code 2 if unable to validate arg1
234 # Depends: yell, printenv, bash 5
235 # Tested on: Debian 10
237 local tzDir returnState
238 if ! [[ $# -eq 1 ]]; then
239 yell
"ERROR:Invalid argument count.";
243 # Read TZDIR env var if available
244 if printenv TZDIR
1>/dev
/null
2>&1; then
245 tzDir
="$(printenv TZDIR)";
247 tzDir
="/usr/share/zoneinfo";
251 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
252 yell
"ERROR:Invalid time zone argument.";
255 # Export ARG1 as TZ environment variable
256 TZ
="$ARG1" && export TZ
&& returnState
="true";
259 # Determine function return code
260 if [ "$returnState" = "true" ]; then
263 } # Exports TZ environment variable
265 # Desc: Report seconds until next day.
267 # Output: stdout: integer seconds until next day
268 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
269 # Usage: timeUntilNextDay
270 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
271 # Depends: date 8, echo 8, yell, try
273 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
275 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
276 TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
277 SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
278 if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then
280 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then
281 returnState
="warning_zero";
282 yell
"WARNING:Reported time until next day exactly zero.";
283 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then
284 returnState
="warning_negative";
285 yell
"WARNING:Reported time until next day is negative.";
288 try
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
290 # Determine function return code
291 if [[ "$returnState" = "true" ]]; then
293 elif [[ "$returnState" = "warning_zero" ]]; then
295 elif [[ "$returnState" = "warning_negative" ]]; then
298 } # Report seconds until next day
300 # Desc: Report seconds until next hour
302 # Output: stdout: integer seconds until next hour
303 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
304 # Usage: timeUntilNextHour
305 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
307 local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
308 TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
309 TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
310 SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second).
311 if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then
313 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then
314 returnState
="warning_zero";
315 yell
"WARNING:Reported time until next hour exactly zero.";
316 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then
317 returnState
="warning_negative";
318 yell
"WARNING:Reported time until next hour is negative.";
321 try
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
323 # Determine function return code
324 if [[ "$returnState" = "true" ]]; then
326 elif [[ "$returnState" = "warning_zero" ]]; then
328 elif [[ "$returnState" = "warning_negative" ]]; then
331 } # Report seconds until next hour
333 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
334 # Usage: dateTimeShort
335 # Output: stdout: timestamp (ISO-8601, no separators)
336 local TIME_CURRENT TIME_CURRENT_SHORT
337 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
338 TIME_CURRENT_SHORT
="$(date -d "$TIME_CURRENT" +%Y%m%dT%H%M%S%z)"; # Produce separator-less current timestamp with resolution 1 second.
339 echo "$TIME_CURRENT_SHORT";
340 } # Get YYYYmmddTHHMMSS±zzzz
342 # Desc: Date without separators (YYYYmmdd)
344 # Output: stdout: date (ISO-8601, no separators)
345 local TIME_CURRENT DATE_CURRENT_SHORT
346 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
347 DATE_CURRENT_SHORT
="$(date -d "$TIME_CURRENT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
348 echo "$DATE_CURRENT_SHORT";
351 # Desc: Output approximate time duration string before given time (default:current date)
352 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
353 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
354 # Usage: timeDuration [arg1] ([arg2])
356 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
357 # arg2: precision level (optional; default=2)
358 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
359 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
360 # Depends: date 8 (gnucoreutils), yell,
361 local returnState ARG1 ARG2 remainder precision witherPrecision
362 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
363 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
364 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
368 precision
=2; # set default precision
369 returnState
="true"; # set default return state
371 # Check that between one and two arguments is supplied
372 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
373 yell
"ERROR:Invalid number of arguments:$# . Exiting.";
374 returnState
="ERROR_INPUT"; fi
376 # Check that arg1 provided
377 if [[ $# -ge 1 ]]; then
378 ## Check that arg1 is a positive integer
379 if [[ "$ARG1" =~ ^
[[:digit
:]]+$
]]; then
382 yell
"ERROR:ARG1 not a digit.";
383 returnState
="ERROR_INPUT";
386 yell
"ERROR:No argument provided. Exiting.";
390 # Consider whether arg2 was provided
391 if [[ $# -eq 2 ]]; then
392 # Check that the second arg is a positive integer
393 if [[ "$ARG2" =~ ^
[[:digit
:]]+$
]] && [[ "$ARG2" -gt 0 ]]; then
396 yell
"ERROR:ARG2 not a positive integer. (is $ARG2 ). Leaving early.";
397 returnState
="ERROR_INPUT";
403 remainder
="$ARG1" ; # seconds
404 ## Calculate full years Y, update remainder
405 fullYears
=$
(( remainder
/ (365*24*60*60) ));
406 remainder
=$
(( remainder
- (fullYears
*365*24*60*60) ));
407 ## Calculate full months M, update remainder
408 fullMonths
=$
(( remainder
/ (30*24*60*60) ));
409 remainder
=$
(( remainder
- (fullMonths
*30*24*60*60) ));
410 ## Calculate full days D, update remainder
411 fullDays
=$
(( remainder
/ (24*60*60) ));
412 remainder
=$
(( remainder
- (fullDays
*24*60*60) ));
413 ## Calculate full hours H, update remainder
414 fullHours
=$
(( remainder
/ (60*60) ));
415 remainder
=$
(( remainder
- (fullHours
*60*60) ));
416 ## Calculate full minutes M, update remainder
417 fullMinutes
=$
(( remainder
/ (60) ));
418 remainder
=$
(( remainder
- (fullMinutes
*60) ));
419 ## Calculate full seconds S, update remainder
420 fullSeconds
=$
(( remainder
/ (1) ));
421 remainder
=$
(( remainder
- (remainder
*1) ));
422 ## Check which fields filled
423 if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi
424 if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi
425 if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi
426 if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi
427 if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi
428 if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi
430 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
431 witherPrecision
="false"
434 if $hasYears && [[ $precision -gt 0 ]]; then
436 witherPrecision
="true";
438 displayYears
="false";
440 if $witherPrecision; then ((precision--
)); fi;
443 if $hasMonths && [[ $precision -gt 0 ]]; then
444 displayMonths
="true";
445 witherPrecision
="true";
447 displayMonths
="false";
449 if $witherPrecision && [[ $precision -gt 0 ]]; then
450 displayMonths
="true";
452 if $witherPrecision; then ((precision--
)); fi;
455 if $hasDays && [[ $precision -gt 0 ]]; then
457 witherPrecision
="true";
461 if $witherPrecision && [[ $precision -gt 0 ]]; then
464 if $witherPrecision; then ((precision--
)); fi;
467 if $hasHours && [[ $precision -gt 0 ]]; then
469 witherPrecision
="true";
471 displayHours
="false";
473 if $witherPrecision && [[ $precision -gt 0 ]]; then
476 if $witherPrecision; then ((precision--
)); fi;
479 if $hasMinutes && [[ $precision -gt 0 ]]; then
480 displayMinutes
="true";
481 witherPrecision
="true";
483 displayMinutes
="false";
485 if $witherPrecision && [[ $precision -gt 0 ]]; then
486 displayMinutes
="true";
488 if $witherPrecision; then ((precision--
)); fi;
492 if $hasSeconds && [[ $precision -gt 0 ]]; then
493 displaySeconds
="true";
494 witherPrecision
="true";
496 displaySeconds
="false";
498 if $witherPrecision && [[ $precision -gt 0 ]]; then
499 displaySeconds
="true";
501 if $witherPrecision; then ((precision--
)); fi;
505 ## Determine whether or not the "T" separator is needed to separate date and time elements
506 if ( $displayHours ||
$displayMinutes ||
$displaySeconds); then
507 displayDateTime
="true"; else displayDateTime
="false"; fi
509 ## Construct duration output string
511 if $displayYears; then
512 OUTPUT
=$OUTPUT$fullYears"Y"; fi
513 if $displayMonths; then
514 OUTPUT
=$OUTPUT$fullMonths"M"; fi
515 if $displayDays; then
516 OUTPUT
=$OUTPUT$fullDays"D"; fi
517 if $displayDateTime; then
518 OUTPUT
=$OUTPUT"T"; fi
519 if $displayHours; then
520 OUTPUT
=$OUTPUT$fullHours"H"; fi
521 if $displayMinutes; then
522 OUTPUT
=$OUTPUT$fullMinutes"M"; fi
523 if $displaySeconds; then
524 OUTPUT
=$OUTPUT$fullSeconds"S"; fi
526 ## Output duration string to stdout
527 if [[ "$returnState" = "true" ]]; then echo "$OUTPUT"; fi
529 #===Determine function return code===
530 if [ "$returnState" = "true" ]; then
533 echo "$returnState" 1>&2;
537 } # Get duration (ex: PT10M4S )
539 # Desc: Displays missing apps, files, and dirs
540 # Usage: displayMissing
541 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
542 # Output: stderr messages
543 #==BEGIN Display errors==
544 #===BEGIN Display Missing Apps===
545 missingApps
="Missing apps :"
546 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
547 for key
in "${!appRollCall[@]}"; do
548 value
="${appRollCall[$key]}"
549 if [ "$value" = "false" ]; then
550 #echo "DEBUG:Missing apps: $key => $value";
551 missingApps
="$missingApps""$key "
555 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
556 echo "$missingApps" 1>&2;
558 #===END Display Missing Apps===
560 #===BEGIN Display Missing Files===
561 missingFiles
="Missing files:"
562 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
563 for key
in "${!fileRollCall[@]}"; do
564 value
="${fileRollCall[$key]}"
565 if [ "$value" = "false" ]; then
566 #echo "DEBUG:Missing files: $key => $value";
567 missingFiles
="$missingFiles""$key "
571 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
572 echo "$missingFiles" 1>&2;
574 #===END Display Missing Files===
576 #===BEGIN Display Missing Directories===
577 missingDirs
="Missing dirs:"
578 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
579 for key
in "${!dirRollCall[@]}"; do
580 value
="${dirRollCall[$key]}"
581 if [ "$value" = "false" ]; then
582 #echo "DEBUG:Missing dirs: $key => $value";
583 missingDirs
="$missingDirs""$key "
587 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
588 echo "$missingDirs" 1>&2;
590 #===END Display Missing Directories===
592 #==END Display errors==
593 } # Display missing apps, files, dirs
595 #Desc: Sets script TTL
596 #Usage: setScriptTTL arg1
597 #Input: arg1: "day" or "hour"
599 #Depends: timeUntilNextHour or timeUntilNextDay
602 if [[ "$ARG1" = "day" ]]; then
603 # Set script lifespan to end at start of next day
604 if ! scriptTTL
="$(timeUntilNextDay)"; then
605 if [[ "$scriptTTL" -eq 0 ]]; then
606 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
608 yell
"ERROR: timeUntilNextDay exit code $?"; exit 1;
611 elif [[ "$ARG1" = "hour" ]]; then
612 # Set script lifespan to end at start of next hour
613 if ! scriptTTL
="$(timeUntilNextHour)"; then
614 if [[ "$scriptTTL" -eq 0 ]]; then
615 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
617 yell
"ERROR: timeUntilNextHour exit code $?"; exit 1;
621 yell
"ERROR:Invalid argument for setScriptTTL function."; exit 1;
623 } # Seconds until next (day|hour).
625 # Desc: Checks that a valid tar archive exists, creates one otherwise
626 # Usage: checkMakeTar [ path ]
628 # Input: arg1: path of tar archive
629 # Output: exit code 0 : tar readable
630 # exit code 1 : tar missing; created
631 # exit code 2 : tar not readable; moved; replaced
632 # Depends: try, tar, date
633 local PATH_TAR returnFlag0 returnFlag1 returnFlag2
636 # Check if file is a valid tar archive
637 if tar --list --file="$PATH_TAR" 1>/dev
/null
2>&1; then
638 ## T1: return success
639 returnFlag0
="tar valid";
641 ## F1: Check if file exists
642 if [[ -f "$PATH_TAR" ]]; then
644 try
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
645 returnFlag1
="tar moved";
650 ## F2: Create tar archive, return 0
651 try
tar --create --file="$PATH_TAR" --files-from=/dev
/null
&& \
652 returnFlag2
="tar created";
655 # Determine function return code
656 if [[ "$returnFlag0" = "tar valid" ]]; then
658 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
659 return 1; # tar missing so created
660 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
661 return 2; # tar not readable so moved; replaced
663 } # checks if arg1 is tar; creates one otherwise
665 # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar
666 # Usage: writeArg "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
667 # Input: arg1: data to be written
668 # arg2: file name of file to be inserted into tar
669 # arg3: tar archive path (must exist first)
670 # arg4: temporary working dir
671 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
672 # Output: file written to disk
673 # Example: decrypt multiple large files in parallel
674 # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
675 # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
676 # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
680 local FN
="${FUNCNAME[0]}"
681 yell
"DEBUG:STATUS:$FN:Finished appendArgTar()."
684 if ! [ -z "$2" ]; then FILENAME
="$2"; else yell
"ERROR:$FN:Not enough arguments."; exit 1; fi
686 # Check tar path is a file
687 if [ -f "$3" ]; then TAR_PATH
="$3"; else yell
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi
690 if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell
"ERROR:$FN:No temporary working dir set."; exit 1; fi
692 # Set command strings
693 if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1
694 if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2
695 if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3
696 if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4
699 yell
"STATUS:$FN:CMD1:$CMD1"
700 yell
"STATUS:$FN:CMD2:$CMD2"
701 yell
"STATUS:$FN:CMD3:$CMD3"
702 yell
"STATUS:$FN:CMD4:$CMD4"
703 yell
"STATUS:$FN:FILENAME:$FILENAME"
704 yell
"STATUS:$FN:TAR_PATH:$TAR_PATH"
705 yell
"STATUS:$FN:TMP_DIR:$TMP_DIR"
706 # Write to temporary working dir
707 echo "$1" |
$CMD1 |
$CMD2 |
$CMD3 |
$CMD4 > "$TMP_DIR"/"$FILENAME";
710 try
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
711 yell
"DEBUG:STATUS:$FN:Finished appendArgTar()."
712 } # Append Bash var to file appended to Tar archive
713 magicWriteVersion
() {
714 # Desc: Appends time-stamped VERSION to PATHOUT_TAR
715 # Usage: magicWriteVersion
716 # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP
717 # Depends: dateTimeShort, appendArgTar
718 local CONTENT_VERSION
720 # Generate VERSION file in BashVar
721 FILEOUT_VERSION
="$(dateTimeShort)..VERSION";
722 CONTENT_VERSION
="VERSION=$SCRIPT_VERSION";
723 CONTENT_VERSION
="$CONTENT_VERSION""\n""SCRIPT_NAME=SCRIPT_NAME";
724 CONTENT_VERSION
="$CONTENT_VERSION""\n""DATE=$(date --iso-8601=seconds)";
725 CONTENT_VERSION
="$CONTENT_VERSION""\n""HOSTNAME=$SCRIPT_HOSTNAME";
726 CONTENT_VERSION
="$(echo -e "$CONTENT_VERSION")"
728 # Create BashVar as file FILEOUT_VERSION and write-append to PATHOUT_TAR
729 appendArgTar
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP";
730 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar()
732 # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR
733 # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP
734 # Depends: yell, try, vbm, appendArgTar, tar
735 local FN
="${FUNCNAME[0]}";
736 wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
737 vbm
"DEBUG:STATUS:$FN:Started magicWriteBuffer().";
739 # Validate PATHOUT_TAR as tar.
740 checkMakeTar
"$PATHOUT_TAR";
741 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
742 if [[ $?
-eq 1 ]] ||
[[ $?
-eq 2 ]]; then magicWriteVersion
; fi
744 # Write bufferBash to PATHOUT_TAR
745 appendArgTar
"$bufferBash" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data
746 appendArgTar
"$bufferBash" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file
747 appendArgTar
"$bufferBash" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file
748 # Remove secured chunks from DIR_TMP
749 try
rm "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML";
750 yell
"DEBUG:STATUS:$FN:Finished magicWriteBuffer().";
751 } # bkgpslog write function
754 processArguments
"$@" # Process arguments.
756 # Determine working directory
757 ## Set DIR_TMP_PARENT to user-specified value if specified
758 if [[ "$OPTION_TMPDIR" = "true" ]]; then
759 if [[ -d "$TMP_DIR_PRIORITY" ]]; then
760 DIR_TMP_PARENT
="$OPTION_TMPDIR";
762 yell
"WARNING:Specified temporary working directory not valid:$OPTION_TMPDIR";
767 ## Set DIR_TMP_PARENT to default or fallback otherwise
768 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
769 DIR_TMP_PARENT
="$DIR_TMP_DEFAULT";
770 elif [[ -d /tmp
]]; then
771 yell
"WARNING:/dev/shm not available. Falling back to /tmp .";
772 DIR_TMP_PARENT
="/tmp";
774 yell
"ERROR:No valid working directory available. Exiting.";
778 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
779 DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().
781 # Set output encryption and compression option strings
782 if [[ "$OPTION_ENCRYPT" = "true" ]]; then # Check if encryption option active.
783 if checkapp age
; then # Check that age is available.
784 for pubkey
in "${recPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
785 vbm
"DEBUG:Testing pubkey string:$pubkey"
786 if echo "butts" | age
-a -r "$pubkey" 1>/dev
/null
; then
787 # Form age recipient string
788 recipients
="$recipients""-r $pubkey ";
789 vbm
"STATUS:Added pubkey for forming age recipient string:""$pubkey";
790 vbm
"DEBUG:recipients:""$recipients";
792 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
795 vbm
"DEBUG:Finished processing recPubKeys array";
796 # Form age command string
797 CMD_ENCRYPT
="age ""$recipients ";
798 CMD_ENCRYPT_SUFFIX
=".age";
800 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
803 CMD_ENCRYPT
="tee /dev/null ";
804 CMD_ENCRYPT_SUFFIX
="";
805 vbm
"DEBUG:Encryption not enabled."
807 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
808 if checkapp
gzip; then # Check if gzip available
809 CMD_COMPRESS
="gzip ";
810 CMD_COMPRESS_SUFFIX
=".gz";
812 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
815 CMD_COMPRESS
="tee /dev/null ";
816 CMD_COMPRESS_SUFFIX
="";
817 vbm
"DEBUG:Compression not enabled.";
820 # Check that critical apps and dirs are available, displag missing ones.
821 if ! checkapp gpspipe
tar && ! checkdir
"$DIR_OUT" "/dev/shm"; then
822 yell
"ERROR:Critical components missing.";
823 displayMissing
; yell
"Exiting."; exit 1; fi
825 # Set script lifespan
826 setScriptTTL
"$SCRIPT_TTL"; # seconds until next new SCRIPT_TTL (ex: "day" or "hour")
828 # File name substring: encoded bufferTTL
829 bufferTTL_STR
="$(timeDuration $BUFFER_TTL)";
831 # Init temp working dir
832 try mkdir
"$DIR_TMP" && vbm
"DEBUG:Working dir creatd at:$DIR_TMP";
834 # Initialize 'tar' archive
835 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
836 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
837 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
838 ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise.
839 checkMakeTar
"$PATHOUT_TAR" && vbm
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR";
840 ## Append VERSION file to PATHOUT_TAR
843 # Record gps data until script lifespan ends
844 declare debugCounter
; debugCounter
="0"; # set debug counter
845 while [[ "$SECONDS" -lt "$scriptTTL" ]]; do
846 timeBufferStart
="$(dateTimeShort)"; # Note start time
847 # Determine file paths (time is start of buffer period)
848 FILEOUT_BASENAME
="$timeBufferStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" && vbm
"STATUS:Set FILEOUT_BASENAME to:$FILEOUT_BASENAME";
849 ## Files saved to DIR_TMP
850 FILEOUT_NMEA
="$FILEOUT_BASENAME".nmea
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_NMEA to:$FILEOUT_NMEA";
851 FILEOUT_GPX
="$FILEOUT_BASENAME".gpx
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_GPX to:$FILEOUT_GPX";
852 FILEOUT_KML
="$FILEOUT_BASENAME".kml
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_KML to:$FILEOUT_KML";
853 PATHOUT_NMEA
="$DIR_TMP"/"$FILEOUT_NMEA" && vbm
"STATUS:Set PATHOUT_NMEA to:$PATHOUT_NMEA";
854 PATHOUT_GPX
="$DIR_TMP"/"$FILEOUT_GPX" && vbm
"STATUS:Set PATHOUT_GPX to:$PATHOUT_GPX";
855 PATHOUT_KML
="$DIR_TMP"/"$FILEOUT_KML" && vbm
"STATUS:Set PATHOUT_KML to:$PATHOUT_KML";
856 ## Files saved to disk (DIR_OUT)
857 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
858 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
859 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
860 # Define GPS conversion commands
861 CMD_CONV_NMEA
="tee /dev/null " && vbm
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough
862 CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX
863 CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML
864 # Fill Bash variable buffer
865 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
866 # Process bufferBash, save secured chunk set to DIR_TMP
867 vbm
"STATUS:Beginning to save data to $DIR_TMP";
869 # Append each secured chunk in memory dir (DIR_TMP) to file on disk (PATHOUT_TAR in DIR_OUT)
870 vbm
"STATUS:DIR_TMP :$DIR_TMP";
871 vbm
"STATUS:PATHOUT_TAR :$PATHOUT_TAR";
872 vbm
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA";
873 vbm
"STATUS:PATHOUT_GPX:$PATHOUT_GPX";
874 vbm
"STATUS:PATHOUT_KML:$PATHOUT_KML";
876 # Reset buffer and filenames
877 unset bufferBash FILEOUT_BASENAME PATHOUT_NMEA PATHOUT_GPX PATHOUT_KML PATHOUT_TAR timeBufferStart
;
878 vbm
"DEBUG:Completed buffer session $debugCounter ." 1>&2;
883 try
rm -r "$DIR_TMP";
885 vbm
"STATUS:Main function finished.";
887 #===END Declare local script functions===
888 #==END Define script parameters==
891 #==BEGIN Perform work and exit==
892 main
"$@" # Run main function.
894 #==END Perform work and exit==