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.0"; # Define version of script.
19 declare -Ag appRollCall
# Associative array for storing app status
20 declare -Ag fileRollCall
# Associative array for storing file status
21 declare -Ag dirRollCall
# Associative array for storing dir status
22 declare -a recPubKeys
# for processArguments function
23 declare recipients
# for main function
25 ## Initialize variables
26 OPTION_VERBOSE
=""; OPTION_ENCRYPT
=""; OPTION_COMPRESS
=""; OPTION_TMPDIR
="";
28 #===BEGIN Declare local script functions===
30 # Desc: If arg is a command, save result in assoc array 'appRollCall'
31 # Usage: checkapp arg1 arg2 arg3 ...
32 # Input: global assoc. array 'appRollCall'
33 # Output: adds/updates key(value) to global assoc array 'appRollCall'
35 #echo "DEBUG:$(date +%S.%N)..Starting checkapp function."
36 #echo "DEBUG:args: $@"
37 #echo "DEBUG:returnState:$returnState"
41 #echo "DEBUG:processing arg:$arg"
42 if command -v "$arg" 1>/dev
/null
2>&1; then # Check if arg is a valid command
43 appRollCall
[$arg]="true";
44 #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]}
45 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
47 appRollCall
[$arg]="false"; returnState
="false";
51 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
52 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
54 #===Determine function return code===
55 if [ "$returnState" = "true" ]; then
56 #echo "DEBUG:checkapp returns true for $arg";
59 #echo "DEBUG:checkapp returns false for $arg";
62 } # Check that app exists
64 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
65 # Usage: checkfile arg1 arg2 arg3 ...
66 # Input: global assoc. array 'fileRollCall'
67 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
68 # Output: returns 0 if app found, 1 otherwise
73 #echo "DEBUG:processing arg:$arg"
74 if [ -f "$arg" ]; then
75 fileRollCall
["$arg"]="true";
76 #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]}
77 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
79 fileRollCall
["$arg"]="false"; returnState
="false";
83 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done
84 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
86 #===Determine function return code===
87 if [ "$returnState" = "true" ]; then
88 #echo "DEBUG:checkapp returns true for $arg";
91 #echo "DEBUG:checkapp returns false for $arg";
94 } # Check that file exists
96 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
97 # Usage: checkdir arg1 arg2 arg3 ...
98 # Input: global assoc. array 'dirRollCall'
99 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
100 # Output: returns 0 if app found, 1 otherwise
105 #echo "DEBUG:processing arg:$arg"
106 if [ -d "$arg" ]; then
107 dirRollCall
["$arg"]="true";
108 #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]}
109 if ! [ "$returnState" = "false" ]; then returnState
="true"; fi
110 elif [ "$arg" = "" ]; then
111 dirRollCall
["$arg"]="false"; returnState
="false";
117 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done
118 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
120 #===Determine function return code===
121 if [ "$returnState" = "true" ]; then
122 #echo "DEBUG:checkapp returns true for $arg";
125 #echo "DEBUG:checkapp returns false for $arg";
128 } # Check that dir exists
130 # Yell, Die, Try Three-Fingered Claw technique
131 # Ref/Attrib: https://stackoverflow.com/a/25515370
132 yell
() { echo "$0: $*" >&2; }
133 die
() { yell
"$*"; exit 111; }
134 try
() { "$@" || die
"cannot $*"; }
137 echo "$@" 1>&2; # Define stderr echo function.
138 } # Define stderr message function.
141 echoerr
" bkgpslog [ options ]"
144 echoerr
" -h, --help"
145 echoerr
" Display help information."
148 echoerr
" Display script version."
150 echoerr
" -v, --verbose"
151 echoerr
" Display debugging info."
153 echoerr
" -e, --encrypt"
154 echoerr
" Encrypt output."
156 echoerr
" -r, --recipient [ pubkey string ]"
157 echoerr
" Specify recipient. May be age or ssh pubkey."
158 echoerr
" See https://github.com/FiloSottile/age"
160 echoerr
" -o, --output [ directory ]"
161 echoerr
" Specify output directory to save logs."
163 echoerr
" -c, --compress"
164 echoerr
" Compress output with gzip (before encryption if enabled)."
166 echoerr
" -z, --time-zone"
167 echoerr
" Specify time zone. (ex: \"America/New_York\")"
169 echoerr
" -t, --temp-dir"
170 echoerr
" Specify parent directory for temporary working directory."
171 echoerr
" Default: \"/dev/shm\""
173 echoerr
"EXAMPLE: (bash script lines)"
174 echoerr
"/bin/bash bkgpslog -e -c \\"
175 echoerr
"-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
176 echoerr
"-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
177 echoerr
"-o ~/Sync/Location"
178 } # Display information on how to use this script.
180 echoerr
"$SCRIPT_VERSION"
181 } # Display script version.
183 # Usage: vbm "DEBUG:verbose message here"
184 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
186 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
187 # - "$@" positional arguments fed to this function.
189 # Script function dependencies: echoerr
190 # External function dependencies: echo
191 # Last modified: 2020-04-11T23:57Z
192 # Last modified by: Steven Baltakatei Sandoval
196 if [ "$OPTION_VERBOSE" = "true" ]; then
197 FUNCTION_TIME
=$
(date --iso-8601=ns
); # Save current time in nano seconds.
198 echoerr
"[$FUNCTION_TIME] ""$*"; # Display argument text.
202 return 0; # Function finished.
203 } # Verbose message display function.
205 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
206 #echoerr "DEBUG:Starting processArguments while loop."
207 #echoerr "DEBUG:Provided arguments are:""$*"
209 -h |
--help) showUsage
; exit 1;; # Display usage.
210 --version) showVersion
; exit 1;; # Show version
211 -v |
--verbose) OPTION_VERBOSE
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode.
212 -o |
--output) if [ -d "$2" ]; then DIR_OUT
="$2"; vbm
"DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory.
213 -e |
--encrypt) OPTION_ENCRYPT
="true"; vbm
"DEBUG:Encrypted output mode enabled.";;
214 -r |
--recipient) # Add 'age' recipient via public key string
215 recPubKeys
+=("$2"); vbm
"STATUS:pubkey added:""$2"; shift;;
216 -c |
--compress) OPTION_COMPRESS
="true"; vbm
"DEBUG:Compressed output mode enabled.";;
217 -z |
--time-zone) try setTimeZoneEV
"$2"; shift;;
218 -t |
--temp-dir) OPTION_TMPDIR
="true" && TMP_DIR_PRIORITY
="$2"; shift;;
219 *) echoerr
"ERROR: Unrecognized argument: $1"; echoerr
"STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
223 } # Argument Processing
225 # Desc: Set time zone environment variable TZ
226 # Usage: setTimeZoneEV arg1
227 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
228 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
230 # exit code 0 on success
231 # exit code 1 on incorrect number of arguments
232 # exit code 2 if unable to validate arg1
233 # Depends: yell, printenv, bash 5
234 # Tested on: Debian 10
236 local tzDir returnState
237 if ! [[ $# -eq 1 ]]; then
238 yell
"ERROR:Invalid argument count.";
242 # Read TZDIR env var if available
243 if printenv TZDIR
1>/dev
/null
2>&1; then
244 tzDir
="$(printenv TZDIR)";
246 tzDir
="/usr/share/zoneinfo";
250 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
251 yell
"ERROR:Invalid time zone argument.";
254 # Export ARG1 as TZ environment variable
255 TZ
="$ARG1" && export TZ
&& returnState
="true";
258 # Determine function return code
259 if [ "$returnState" = "true" ]; then
262 } # Exports TZ environment variable
264 # Desc: Report seconds until next day.
266 # Output: stdout: integer seconds until next day
267 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
268 # Usage: timeUntilNextDay
269 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
270 # Depends: date 8, echo 8, yell, try
272 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
274 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
275 TIME_NEXT_DAY
="$(date -d "$TIME_CURRENT next day
" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
276 SECONDS_UNTIL_NEXT_DAY
="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
277 if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then
279 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then
280 returnState
="warning_zero";
281 yell
"WARNING:Reported time until next day exactly zero.";
282 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then
283 returnState
="warning_negative";
284 yell
"WARNING:Reported time until next day is negative.";
287 try
echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
289 # Determine function return code
290 if [[ "$returnState" = "true" ]]; then
292 elif [[ "$returnState" = "warning_zero" ]]; then
294 elif [[ "$returnState" = "warning_negative" ]]; then
297 } # Report seconds until next day
299 # Desc: Report seconds until next hour
301 # Output: stdout: integer seconds until next hour
302 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
303 # Usage: timeUntilNextHour
304 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
306 local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
307 TIME_CURRENT
="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
308 TIME_NEXT_HOUR
="$(date -d "$TIME_CURRENT next hour
" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
309 SECONDS_UNTIL_NEXT_HOUR
="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second).
310 if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then
312 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then
313 returnState
="warning_zero";
314 yell
"WARNING:Reported time until next hour exactly zero.";
315 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then
316 returnState
="warning_negative";
317 yell
"WARNING:Reported time until next hour is negative.";
320 try
echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
322 # Determine function return code
323 if [[ "$returnState" = "true" ]]; then
325 elif [[ "$returnState" = "warning_zero" ]]; then
327 elif [[ "$returnState" = "warning_negative" ]]; then
330 } # Report seconds until next hour
332 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
333 # Usage: dateTimeShort
334 # Output: stdout: timestamp (ISO-8601, no separators)
335 local TIME_CURRENT TIME_CURRENT_SHORT
336 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
337 TIME_CURRENT_SHORT
="$(date -d "$TIME_CURRENT" +%Y%m%dT%H%M%S%z)"; # Produce separator-less current timestamp with resolution 1 second.
338 echo "$TIME_CURRENT_SHORT";
339 } # Get YYYYmmddTHHMMSS±zzzz
341 # Desc: Date without separators (YYYYmmdd)
343 # Output: stdout: date (ISO-8601, no separators)
344 local TIME_CURRENT DATE_CURRENT_SHORT
345 TIME_CURRENT
="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
346 DATE_CURRENT_SHORT
="$(date -d "$TIME_CURRENT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
347 echo "$DATE_CURRENT_SHORT";
350 # Desc: Output approximate time duration string before given time (default:current date)
351 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
352 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
353 # Usage: timeDuration [arg1] ([arg2])
355 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
356 # arg2: precision level (optional; default=2)
357 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
358 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
359 # Depends: date 8 (gnucoreutils), yell,
360 local returnState ARG1 ARG2 remainder precision witherPrecision
361 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
362 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
363 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
367 precision
=2; # set default precision
368 returnState
="true"; # set default return state
370 # Check that between one and two arguments is supplied
371 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
372 yell
"ERROR:Invalid number of arguments:$# . Exiting.";
373 returnState
="ERROR_INPUT"; fi
375 # Check that arg1 provided
376 if [[ $# -ge 1 ]]; then
377 ## Check that arg1 is a positive integer
378 if [[ "$ARG1" =~ ^
[[:digit
:]]+$
]]; then
381 yell
"ERROR:ARG1 not a digit.";
382 returnState
="ERROR_INPUT";
385 yell
"ERROR:No argument provided. Exiting.";
389 # Consider whether arg2 was provided
390 if [[ $# -eq 2 ]]; then
391 # Check that the second arg is a positive integer
392 if [[ "$ARG2" =~ ^
[[:digit
:]]+$
]] && [[ "$ARG2" -gt 0 ]]; then
395 yell
"ERROR:ARG2 not a positive integer. (is $ARG2 ). Leaving early.";
396 returnState
="ERROR_INPUT";
402 remainder
="$ARG1" ; # seconds
403 ## Calculate full years Y, update remainder
404 fullYears
=$
(( remainder
/ (365*24*60*60) ));
405 remainder
=$
(( remainder
- (fullYears
*365*24*60*60) ));
406 ## Calculate full months M, update remainder
407 fullMonths
=$
(( remainder
/ (30*24*60*60) ));
408 remainder
=$
(( remainder
- (fullMonths
*30*24*60*60) ));
409 ## Calculate full days D, update remainder
410 fullDays
=$
(( remainder
/ (24*60*60) ));
411 remainder
=$
(( remainder
- (fullDays
*24*60*60) ));
412 ## Calculate full hours H, update remainder
413 fullHours
=$
(( remainder
/ (60*60) ));
414 remainder
=$
(( remainder
- (fullHours
*60*60) ));
415 ## Calculate full minutes M, update remainder
416 fullMinutes
=$
(( remainder
/ (60) ));
417 remainder
=$
(( remainder
- (fullMinutes
*60) ));
418 ## Calculate full seconds S, update remainder
419 fullSeconds
=$
(( remainder
/ (1) ));
420 remainder
=$
(( remainder
- (remainder
*1) ));
421 ## Check which fields filled
422 if [[ $fullYears -gt 0 ]]; then hasYears
="true"; else hasYears
="false"; fi
423 if [[ $fullMonths -gt 0 ]]; then hasMonths
="true"; else hasMonths
="false"; fi
424 if [[ $fullDays -gt 0 ]]; then hasDays
="true"; else hasDays
="false"; fi
425 if [[ $fullHours -gt 0 ]]; then hasHours
="true"; else hasHours
="false"; fi
426 if [[ $fullMinutes -gt 0 ]]; then hasMinutes
="true"; else hasMinutes
="false"; fi
427 if [[ $fullSeconds -gt 0 ]]; then hasSeconds
="true"; else hasSeconds
="false"; fi
429 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
430 witherPrecision
="false"
433 if $hasYears && [[ $precision -gt 0 ]]; then
435 witherPrecision
="true";
437 displayYears
="false";
439 if $witherPrecision; then ((precision--
)); fi;
442 if $hasMonths && [[ $precision -gt 0 ]]; then
443 displayMonths
="true";
444 witherPrecision
="true";
446 displayMonths
="false";
448 if $witherPrecision && [[ $precision -gt 0 ]]; then
449 displayMonths
="true";
451 if $witherPrecision; then ((precision--
)); fi;
454 if $hasDays && [[ $precision -gt 0 ]]; then
456 witherPrecision
="true";
460 if $witherPrecision && [[ $precision -gt 0 ]]; then
463 if $witherPrecision; then ((precision--
)); fi;
466 if $hasHours && [[ $precision -gt 0 ]]; then
468 witherPrecision
="true";
470 displayHours
="false";
472 if $witherPrecision && [[ $precision -gt 0 ]]; then
475 if $witherPrecision; then ((precision--
)); fi;
478 if $hasMinutes && [[ $precision -gt 0 ]]; then
479 displayMinutes
="true";
480 witherPrecision
="true";
482 displayMinutes
="false";
484 if $witherPrecision && [[ $precision -gt 0 ]]; then
485 displayMinutes
="true";
487 if $witherPrecision; then ((precision--
)); fi;
491 if $hasSeconds && [[ $precision -gt 0 ]]; then
492 displaySeconds
="true";
493 witherPrecision
="true";
495 displaySeconds
="false";
497 if $witherPrecision && [[ $precision -gt 0 ]]; then
498 displaySeconds
="true";
500 if $witherPrecision; then ((precision--
)); fi;
504 ## Determine whether or not the "T" separator is needed to separate date and time elements
505 if ( $displayHours ||
$displayMinutes ||
$displaySeconds); then
506 displayDateTime
="true"; else displayDateTime
="false"; fi
508 ## Construct duration output string
510 if $displayYears; then
511 OUTPUT
=$OUTPUT$fullYears"Y"; fi
512 if $displayMonths; then
513 OUTPUT
=$OUTPUT$fullMonths"M"; fi
514 if $displayDays; then
515 OUTPUT
=$OUTPUT$fullDays"D"; fi
516 if $displayDateTime; then
517 OUTPUT
=$OUTPUT"T"; fi
518 if $displayHours; then
519 OUTPUT
=$OUTPUT$fullHours"H"; fi
520 if $displayMinutes; then
521 OUTPUT
=$OUTPUT$fullMinutes"M"; fi
522 if $displaySeconds; then
523 OUTPUT
=$OUTPUT$fullSeconds"S"; fi
525 ## Output duration string to stdout
526 if [[ "$returnState" = "true" ]]; then echo "$OUTPUT"; fi
528 #===Determine function return code===
529 if [ "$returnState" = "true" ]; then
532 echo "$returnState" 1>&2;
536 } # Get duration (ex: PT10M4S )
538 # Desc: Displays missing apps, files, and dirs
539 # Usage: displayMissing
540 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
541 # Output: stderr messages
542 #==BEGIN Display errors==
543 #===BEGIN Display Missing Apps===
544 missingApps
="Missing apps :"
545 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
546 for key
in "${!appRollCall[@]}"; do
547 value
="${appRollCall[$key]}"
548 if [ "$value" = "false" ]; then
549 #echo "DEBUG:Missing apps: $key => $value";
550 missingApps
="$missingApps""$key "
554 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
555 echo "$missingApps" 1>&2;
557 #===END Display Missing Apps===
559 #===BEGIN Display Missing Files===
560 missingFiles
="Missing files:"
561 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
562 for key
in "${!fileRollCall[@]}"; do
563 value
="${fileRollCall[$key]}"
564 if [ "$value" = "false" ]; then
565 #echo "DEBUG:Missing files: $key => $value";
566 missingFiles
="$missingFiles""$key "
570 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
571 echo "$missingFiles" 1>&2;
573 #===END Display Missing Files===
575 #===BEGIN Display Missing Directories===
576 missingDirs
="Missing dirs:"
577 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
578 for key
in "${!dirRollCall[@]}"; do
579 value
="${dirRollCall[$key]}"
580 if [ "$value" = "false" ]; then
581 #echo "DEBUG:Missing dirs: $key => $value";
582 missingDirs
="$missingDirs""$key "
586 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
587 echo "$missingDirs" 1>&2;
589 #===END Display Missing Directories===
591 #==END Display errors==
592 } # Display missing apps, files, dirs
594 #Desc: Sets script TTL
595 #Usage: setScriptTTL arg1
596 #Input: arg1: "day" or "hour"
598 #Depends: timeUntilNextHour or timeUntilNextDay
601 if [[ "$ARG1" = "day" ]]; then
602 # Set script lifespan to end at start of next day
603 if ! scriptTTL
="$(timeUntilNextDay)"; then
604 if [[ "$scriptTTL" -eq 0 ]]; then
605 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
607 yell
"ERROR: timeUntilNextDay exit code $?"; exit 1;
610 elif [[ "$ARG1" = "hour" ]]; then
611 # Set script lifespan to end at start of next hour
612 if ! scriptTTL
="$(timeUntilNextHour)"; then
613 if [[ "$scriptTTL" -eq 0 ]]; then
614 ((scriptTTL
++)); # Add 1 because 0 would cause 'timeout' to never timeout.
616 yell
"ERROR: timeUntilNextHour exit code $?"; exit 1;
620 yell
"ERROR:Invalid argument for setScriptTTL function."; exit 1;
622 } # Seconds until next (day|hour).
624 # Desc: Checks that a valid tar archive exists, creates one otherwise
625 # Usage: checkMakeTar [ path ]
627 # Input: arg1: path of tar archive
628 # Output: exit code 0 : tar readable
629 # exit code 1 : tar missing; created
630 # exit code 2 : tar not readable; moved; replaced
631 # Depends: try, tar, date
632 local PATH_TAR returnFlag0 returnFlag1 returnFlag2
635 # Check if file is a valid tar archive
636 if tar --list --file="$PATH_TAR" 1>/dev
/null
2>&1; then
637 ## T1: return success
638 returnFlag0
="tar valid";
640 ## F1: Check if file exists
641 if [[ -f "$PATH_TAR" ]]; then
643 try
mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
644 returnFlag1
="tar moved";
649 ## F2: Create tar archive, return 0
650 try
tar --create --file="$PATH_TAR" --files-from=/dev
/null
&& \
651 returnFlag2
="tar created";
654 # Determine function return code
655 if [[ "$returnFlag0" = "tar valid" ]]; then
657 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
658 return 1; # tar missing so created
659 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
660 return 2; # tar not readable so moved; replaced
662 } # checks if arg1 is tar; creates one otherwise
664 # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar
665 # Usage: writeArg "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
666 # Input: arg1: data to be written
667 # arg2: file name of file to be inserted into tar
668 # arg3: tar archive path (must exist first)
669 # arg4: temporary working dir
670 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
671 # Output: file written to disk
672 # Example: decrypt multiple large files in parallel
673 # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
674 # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
675 # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
679 local FN
="${FUNCNAME[0]}"
680 yell
"DEBUG:STATUS:$FN:Finished appendArgTar()."
683 if ! [ -z "$2" ]; then FILENAME
="$2"; else yell
"ERROR:$FN:Not enough arguments."; exit 1; fi
685 # Check tar path is a file
686 if [ -f "$3" ]; then TAR_PATH
="$3"; else yell
"ERROR:$FN:Tar archive arg not a file."; exit 1; fi
689 if ! [ -z "$4" ]; then TMP_DIR
="$4"; else yell
"ERROR:$FN:No temporary working dir set."; exit 1; fi
691 # Set command strings
692 if ! [ -z "$5" ]; then CMD1
="$5"; else CMD1
="tee /dev/null "; fi # command string 1
693 if ! [ -z "$6" ]; then CMD2
="$6"; else CMD2
="tee /dev/null "; fi # command string 2
694 if ! [ -z "$7" ]; then CMD3
="$7"; else CMD3
="tee /dev/null "; fi # command string 3
695 if ! [ -z "$8" ]; then CMD4
="$8"; else CMD4
="tee /dev/null "; fi # command string 4
698 yell
"STATUS:$FN:CMD1:$CMD1"
699 yell
"STATUS:$FN:CMD2:$CMD2"
700 yell
"STATUS:$FN:CMD3:$CMD3"
701 yell
"STATUS:$FN:CMD4:$CMD4"
702 yell
"STATUS:$FN:FILENAME:$FILENAME"
703 yell
"STATUS:$FN:TAR_PATH:$TAR_PATH"
704 yell
"STATUS:$FN:TMP_DIR:$TMP_DIR"
705 # Write to temporary working dir
706 echo "$1" |
$CMD1 |
$CMD2 |
$CMD3 |
$CMD4 > "$TMP_DIR"/"$FILENAME";
709 try
tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
710 yell
"DEBUG:STATUS:$FN:Finished appendArgTar()."
711 } # Append Bash var to file appended to Tar archive
712 magicWriteVersion
() {
713 # Desc: Appends time-stamped VERSION to PATHOUT_TAR
714 # Usage: magicWriteVersion
715 # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP
716 # Depends: dateTimeShort, appendArgTar
718 # Generate VERSION file in BashVar
719 FILEOUT_VERSION
="$(dateTimeShort)..VERSION";
720 CONTENT_VERSION
="$(dateTimeShort):$(basename "$0")"" Version:""$SCRIPT_VERSION";
721 # Create BashVar as file FILEOUT_VERSION and write-append to PATHOUT_TAR
722 appendArgTar
"$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP";
723 } # bkgpslog: write version data to PATHOUT_TAR via appendArgTar()
725 # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR
726 # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP
727 # Depends: yell, try, vbm, appendArgTar, tar
728 local FN
="${FUNCNAME[0]}";
729 wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
730 vbm
"DEBUG:STATUS:$FN:Started magicWriteBuffer().";
732 # Validate PATHOUT_TAR as tar.
733 checkMakeTar
"$PATHOUT_TAR";
734 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
735 if [[ $?
-eq 1 ]] ||
[[ $?
-eq 2 ]]; then magicWriteVersion
; fi
737 # Write bufferBash to PATHOUT_TAR
738 appendArgTar
"$bufferBash" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data
739 appendArgTar
"$bufferBash" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file
740 appendArgTar
"$bufferBash" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file
741 # Remove secured chunks from DIR_TMP
742 try
rm "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML";
743 yell
"DEBUG:STATUS:$FN:Finished magicWriteBuffer().";
744 } # bkgpslog write function
747 processArguments
"$@" # Process arguments.
749 # Determine working directory
750 ## Set DIR_TMP_PARENT to user-specified value if specified
751 if [[ "$OPTION_TMPDIR" = "true" ]]; then
752 if [[ -d "$TMP_DIR_PRIORITY" ]]; then
753 DIR_TMP_PARENT
="$OPTION_TMPDIR";
755 yell
"WARNING:Specified temporary working directory not valid:$OPTION_TMPDIR";
760 ## Set DIR_TMP_PARENT to default or fallback otherwise
761 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
762 DIR_TMP_PARENT
="$DIR_TMP_DEFAULT";
763 elif [[ -d /tmp
]]; then
764 yell
"WARNING:/dev/shm not available. Falling back to /tmp .";
765 DIR_TMP_PARENT
="/tmp";
767 yell
"ERROR:No valid working directory available. Exiting.";
771 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
772 DIR_TMP
="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm
"DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().
774 # Set output encryption and compression option strings
775 if [[ "$OPTION_ENCRYPT" = "true" ]]; then # Check if encryption option active.
776 if checkapp age
; then # Check that age is available.
777 for pubkey
in "${recPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
778 vbm
"DEBUG:Testing pubkey string:$pubkey"
779 if echo "butts" | age
-a -r "$pubkey" 1>/dev
/null
; then
780 # Form age recipient string
781 recipients
="$recipients""-r $pubkey ";
782 vbm
"STATUS:Added pubkey for forming age recipient string:""$pubkey";
783 vbm
"DEBUG:recipients:""$recipients";
785 yell
"ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
788 vbm
"DEBUG:Finished processing recPubKeys array";
789 # Form age command string
790 CMD_ENCRYPT
="age ""$recipients ";
791 CMD_ENCRYPT_SUFFIX
=".age";
793 yell
"ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
796 CMD_ENCRYPT
="tee /dev/null ";
797 CMD_ENCRYPT_SUFFIX
="";
798 vbm
"DEBUG:Encryption not enabled."
800 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
801 if checkapp
gzip; then # Check if gzip available
802 CMD_COMPRESS
="gzip ";
803 CMD_COMPRESS_SUFFIX
=".gz";
805 yell
"ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
808 CMD_COMPRESS
="tee /dev/null ";
809 CMD_COMPRESS_SUFFIX
="";
810 vbm
"DEBUG:Compression not enabled.";
813 # Check that critical apps and dirs are available, displag missing ones.
814 if ! checkapp gpspipe
tar && ! checkdir
"$DIR_OUT" "/dev/shm"; then
815 yell
"ERROR:Critical components missing.";
816 displayMissing
; yell
"Exiting."; exit 1; fi
818 # Set script lifespan
819 setScriptTTL
"$SCRIPT_TTL"; # seconds until next new SCRIPT_TTL (ex: "day" or "hour")
821 # File name substring: encoded bufferTTL
822 bufferTTL_STR
="$(timeDuration $BUFFER_TTL)";
824 # Init temp working dir
825 try mkdir
"$DIR_TMP" && vbm
"DEBUG:Working dir creatd at:$DIR_TMP";
827 # Initialize 'tar' archive
828 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
829 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
830 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
831 ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise.
832 checkMakeTar
"$PATHOUT_TAR" && vbm
"DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR";
833 ## Append VERSION file to PATHOUT_TAR
836 # Record gps data until script lifespan ends
837 declare debugCounter
; debugCounter
="0"; # set debug counter
838 while [[ "$SECONDS" -lt "$scriptTTL" ]]; do
839 timeBufferStart
="$(dateTimeShort)"; # Note start time
840 # Determine file paths (time is start of buffer period)
841 FILEOUT_BASENAME
="$timeBufferStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" && vbm
"STATUS:Set FILEOUT_BASENAME to:$FILEOUT_BASENAME";
842 ## Files saved to DIR_TMP
843 FILEOUT_NMEA
="$FILEOUT_BASENAME".nmea
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_NMEA to:$FILEOUT_NMEA";
844 FILEOUT_GPX
="$FILEOUT_BASENAME".gpx
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_GPX to:$FILEOUT_GPX";
845 FILEOUT_KML
="$FILEOUT_BASENAME".kml
"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm
"STATUS:Set FILEOUT_KML to:$FILEOUT_KML";
846 PATHOUT_NMEA
="$DIR_TMP"/"$FILEOUT_NMEA" && vbm
"STATUS:Set PATHOUT_NMEA to:$PATHOUT_NMEA";
847 PATHOUT_GPX
="$DIR_TMP"/"$FILEOUT_GPX" && vbm
"STATUS:Set PATHOUT_GPX to:$PATHOUT_GPX";
848 PATHOUT_KML
="$DIR_TMP"/"$FILEOUT_KML" && vbm
"STATUS:Set PATHOUT_KML to:$PATHOUT_KML";
849 ## Files saved to disk (DIR_OUT)
850 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
851 PATHOUT_TAR
="$DIR_OUT"/"$(dateShort)"..
"$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".
tar && \
852 vbm
"STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
853 # Define GPS conversion commands
854 CMD_CONV_NMEA
="tee /dev/null " && vbm
"STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough
855 CMD_CONV_GPX
="gpsbabel -i nmea -f - -o gpx -F - " && vbm
"STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX
856 CMD_CONV_KML
="gpsbabel -i nmea -f - -o kml -F - " && vbm
"STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML
857 # Fill Bash variable buffer
858 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
859 # Process bufferBash, save secured chunk set to DIR_TMP
860 vbm
"STATUS:Beginning to save data to $DIR_TMP";
862 # Append each secured chunk in memory dir (DIR_TMP) to file on disk (PATHOUT_TAR in DIR_OUT)
863 vbm
"STATUS:DIR_TMP :$DIR_TMP";
864 vbm
"STATUS:PATHOUT_TAR :$PATHOUT_TAR";
865 vbm
"STATUS:PATHOUT_NMEA:$PATHOUT_NMEA";
866 vbm
"STATUS:PATHOUT_GPX:$PATHOUT_GPX";
867 vbm
"STATUS:PATHOUT_KML:$PATHOUT_KML";
869 # Reset buffer and filenames
870 unset bufferBash FILEOUT_BASENAME PATHOUT_NMEA PATHOUT_GPX PATHOUT_KML PATHOUT_TAR timeBufferStart
;
871 vbm
"DEBUG:Completed buffer session $debugCounter ." 1>&2;
876 try
rm -r "$DIR_TMP";
878 vbm
"STATUS:Main function finished.";
880 #===END Declare local script functions===
881 #==END Define script parameters==
884 #==BEGIN Perform work and exit==
885 main
"$@" # Run main function.
887 #==END Perform work and exit==