#!/bin/bash # Desc: Records gps data #==BEGIN Define script parameters== ## Logging Behavior parameters bufferTTL="300"; # time between file writes scriptTTL_TE="day"; # (day|hour) #### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option dirTmpDefault="/dev/shm"; # Default parent of working directory scriptTimeStart=$(date +%Y%m%dT%H%M%S.%N); PATH="$HOME/.local/bin:$PATH"; # Add "$(systemd-path user-binaries)" path in case apps saved there scriptHostname=$(hostname); # Save hostname of system running this script. scriptVersion="0.5.8"; # Define version of script. scriptName="bkgpslog"; # Define basename of script file. scriptURL="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script. ageVersion="1.0.0-beta2"; # Define version of age (encryption program) ageURL="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age. declare -Ag appRollCall # Associative array for storing app status declare -Ag fileRollCall # Associative array for storing file status declare -Ag dirRollCall # Associative array for storing dir status declare -a argRecPubKeys # for processArguments function # declare -a errorHistory # for correcting buffer lag ## Initialize variables optionVerbose=""; optionEncrypt=""; optionCompress=""; optionTmpDir=""; errReset=0; bufferTTL_AdjFloat=""; scriptTTL=""; #===BEGIN Declare local script functions=== yell() { echo "$0: $*" >&2; } #o Yell, Die, Try Three-Fingered Claw technique die() { yell "$*"; exit 111; } #o Ref/Attrib: https://stackoverflow.com/a/25515370 try() { "$@" || die "cannot $*"; } #o checkapp() { # Desc: If arg is a command, save result in assoc array 'appRollCall' # Usage: checkapp arg1 arg2 arg3 ... # Input: global assoc. array 'appRollCall' # Output: adds/updates key(value) to global assoc array 'appRollCall' local returnState #echo "DEBUG:$(date +%S.%N)..Starting checkapp function." #echo "DEBUG:args: $@" #echo "DEBUG:returnState:$returnState" #===Process Args=== for arg in "$@"; do #echo "DEBUG:processing arg:$arg" if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command appRollCall[$arg]="true"; #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]} if ! [ "$returnState" = "false" ]; then returnState="true"; fi else appRollCall[$arg]="false"; returnState="false"; fi done #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done #echo "DEBUG:evaluating returnstate. returnState:"$returnState #===Determine function return code=== if [ "$returnState" = "true" ]; then #echo "DEBUG:checkapp returns true for $arg"; return 0; else #echo "DEBUG:checkapp returns false for $arg"; return 1; fi } # Check that app exists checkfile() { # Desc: If arg is a file path, save result in assoc array 'fileRollCall' # Usage: checkfile arg1 arg2 arg3 ... # Input: global assoc. array 'fileRollCall' # Output: adds/updates key(value) to global assoc array 'fileRollCall'; # Output: returns 0 if app found, 1 otherwise local returnState #===Process Args=== for arg in "$@"; do #echo "DEBUG:processing arg:$arg" if [ -f "$arg" ]; then fileRollCall["$arg"]="true"; #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]} if ! [ "$returnState" = "false" ]; then returnState="true"; fi else fileRollCall["$arg"]="false"; returnState="false"; fi done #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done #echo "DEBUG:evaluating returnstate. returnState:"$returnState #===Determine function return code=== if [ "$returnState" = "true" ]; then #echo "DEBUG:checkapp returns true for $arg"; return 0; else #echo "DEBUG:checkapp returns false for $arg"; return 1; fi } # Check that file exists checkdir() { # Desc: If arg is a dir path, save result in assoc array 'dirRollCall' # Usage: checkdir arg1 arg2 arg3 ... # Input: global assoc. array 'dirRollCall' # Output: adds/updates key(value) to global assoc array 'dirRollCall'; # Output: returns 0 if app found, 1 otherwise local returnState #===Process Args=== for arg in "$@"; do #echo "DEBUG:processing arg:$arg" if [ -d "$arg" ]; then dirRollCall["$arg"]="true"; #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]} if ! [ "$returnState" = "false" ]; then returnState="true"; fi elif [ "$arg" = "" ]; then dirRollCall["$arg"]="false"; returnState="false"; else returnState="false"; fi done #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done #echo "DEBUG:evaluating returnstate. returnState:"$returnState #===Determine function return code=== if [ "$returnState" = "true" ]; then #echo "DEBUG:checkapp returns true for $arg"; return 0; else #echo "DEBUG:checkapp returns false for $arg"; return 1; fi } # Check that dir exists echoerr() { echo "$@" 1>&2; # Define stderr echo function. } # Define stderr message function. showUsage() { echoerr "USAGE:" echoerr " bkgpslog [ options ]" echoerr echoerr "OPTIONS:" echoerr " -h, --help" echoerr " Display help information." echoerr " --version" echoerr " Display script version." echoerr " -v, --verbose" echoerr " Display debugging info." echoerr " -e, --encrypt" echoerr " Encrypt output." echoerr " -r, --recipient [ string pubkey ]" echoerr " Specify recipient. May be age or ssh pubkey." echoerr " May be specified multiple times for multiple pubkeys." echoerr " See https://github.com/FiloSottile/age" echoerr " -o, --output [ path dir ]" echoerr " Specify output directory to save logs." echoerr " -c, --compress" echoerr " Compress output with gzip (before encryption if enabled)." echoerr " -z, --time-zone" echoerr " Specify time zone. (ex: \"America/New_York\")" echoerr " -t, --temp-dir [path dir]" echoerr " Specify parent directory for temporary working directory." echoerr " Default: \"/dev/shm\"" echoerr " -R, --recipient-dir [path dir]" echoerr " Specify directory containing files whose first lines are" echoerr " to be interpreted as pubkey strings (see \\'-r\\' option)." echoerr " -b, --buffer-ttl [integer]" echoerr " Specify custom buffer period in seconds (default: 300 seconds)" echoerr " -B, --script-ttl [integer]" echoerr " Specify custom script time-to-live in seconds (default: \"day\")" echoerr echoerr "EXAMPLE: (bash script lines)" echoerr "/bin/bash bkgpslog -v -e -c \\" echoerr "-z \"UTC\" -t \"/dev/shm\" \\" echoerr "-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\" echoerr "-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\" echoerr "-o ~/Sync/Location" } # Display information on how to use this script. showVersion() { echoerr "$scriptVersion" } # Display script version. vbm() { # Description: Prints verbose message ("vbm") to stderr if optionVerbose is set to "true". # Usage: vbm "DEBUG:verbose message here" # Version 0.1.1 # Input: arg1: string # vars: optionVerbose # Output: stderr # Depends: echo 8, date 8 if [ "$optionVerbose" = "true" ]; then functionTime=$(date --iso-8601=ns); # Save current time in nano seconds. echo "[$functionTime] ""$*" 1>&2; # Display argument text. fi # End function return 0; # Function finished. } # Displays message if optionVerbose true processArguments() { while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0). #echoerr "DEBUG:Starting processArguments while loop." #echoerr "DEBUG:Provided arguments are:""$*" case "$1" in -h | --help) showUsage; exit 1;; # Display usage. --version) showVersion; exit 1;; # Show version -v | --verbose) optionVerbose="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode. -o | --output) if [ -d "$2" ]; then dirOut="$2"; vbm "DEBUG:dirOut:$dirOut"; shift; fi ;; # Define output directory. -e | --encrypt) optionEncrypt="true"; vbm "DEBUG:Encrypted output mode enabled.";; # Enable encryption -r | --recipient) optionRecipients="true"; argRecPubKeys+=("$2"); vbm "STATUS:pubkey added:""$2"; shift;; # Add recipients -c | --compress) optionCompress="true"; vbm "DEBUG:Compressed output mode enabled.";; # Enable compression -z | --time-zone) try setTimeZoneEV "$2"; shift;; # Set timestamp timezone -t | --temp-dir) optionTmpDir="true" && argTempDirPriority="$2"; shift;; # Set time zone -R | --recipient-dir) optionRecipients="true"; optionRecDir="true" && argRecDir="$2"; shift;; # Add recipient watch dir -b | --buffer-ttl) optionCustomBufferTTL="true" && argCustomBufferTTL="$2"; shift;; # Set custom buffer period (default: 300 seconds) -B | --script-ttl) optionCustomScriptTTL_TE="true" && argCustomScriptTTL="$2"; shift;; # Set custom script TTL (default: "day") *) echoerr "ERROR: Unrecognized argument: $1"; echoerr "STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options. esac shift done } # Argument Processing setTimeZoneEV(){ # Desc: Set time zone environment variable TZ # Usage: setTimeZoneEV arg1 # Version 0.1.1 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York") # TZDIR env var (optional; default: "/usr/share/zoneinfo") # Output: exports TZ # exit code 0 on success # exit code 1 on incorrect number of arguments # exit code 2 if unable to validate arg1 # Depends: yell, printenv, bash 5 # Tested on: Debian 10 local tzDir returnState argTimeZone argTimeZone="$1" if ! [[ $# -eq 1 ]]; then yell "ERROR:Invalid argument count."; return 1; fi # Read TZDIR env var if available if printenv TZDIR 1>/dev/null 2>&1; then tzDir="$(printenv TZDIR)"; else tzDir="/usr/share/zoneinfo"; fi # Validate TZ string if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then yell "ERROR:Invalid time zone argument."; return 2; else # Export ARG1 as TZ environment variable TZ="$argTimeZone" && export TZ && returnState="true"; fi # Determine function return code if [ "$returnState" = "true" ]; then return 0; fi } # Exports TZ environment variable timeUntilNextDay(){ # Desc: Report seconds until next day. # Version: 1.0.2 # Output: stdout: integer seconds until next day # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 # Usage: timeUntilNextDay # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi # Depends: date 8, echo 8, yell, try local returnState timeCurrent timeNextDay secondsUntilNextDay returnState timeCurrent="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. timeNextDay="$(date -d "$timeCurrent next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second. secondsUntilNextDay="$(( $(date +%s -d "$timeNextDay") - $(date +%s -d "$timeCurrent") ))" ; # Calculate seconds until closest future midnight (res. 1 second). if [[ "$secondsUntilNextDay" -gt 0 ]]; then returnState="true"; elif [[ "$secondsUntilNextDay" -eq 0 ]]; then returnState="warning_zero"; yell "WARNING:Reported time until next day exactly zero."; elif [[ "$secondsUntilNextDay" -lt 0 ]]; then returnState="warning_negative"; yell "WARNING:Reported time until next day is negative."; fi try echo "$secondsUntilNextDay"; # Report # Determine function return code if [[ "$returnState" = "true" ]]; then return 0; elif [[ "$returnState" = "warning_zero" ]]; then return 1; elif [[ "$returnState" = "warning_negative" ]]; then return 2; fi } # Report seconds until next day timeUntilNextHour(){ # Desc: Report seconds until next hour # Version 1.0.1 # Output: stdout: integer seconds until next hour # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0 # Usage: timeUntilNextHour # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi local returnState timeCurrent timeNextHour secondsUntilNextHour timeCurrent="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second. timeNextHour="$(date -d "$timeCurrent next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second. secondsUntilNextHour="$(( $(date +%s -d "$timeNextHour") - $(date +%s -d "$timeCurrent") ))"; # Calculate seconds until next hour (res. 1 second). if [[ "$secondsUntilNextHour" -gt 0 ]]; then returnState="true"; elif [[ "$secondsUntilNextHour" -eq 0 ]]; then returnState="warning_zero"; yell "WARNING:Reported time until next hour exactly zero."; elif [[ "$secondsUntilNextHour" -lt 0 ]]; then returnState="warning_negative"; yell "WARNING:Reported time until next hour is negative."; fi; try echo "$secondsUntilNextHour"; # Report # Determine function return code if [[ "$returnState" = "true" ]]; then return 0; elif [[ "$returnState" = "warning_zero" ]]; then return 1; elif [[ "$returnState" = "warning_negative" ]]; then return 2; fi; } # Report seconds until next hour dateTimeShort(){ # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz) # Usage: dateTimeShort ([str date]) # Version 1.1.1 # Input: arg1: 'date'-parsable timestamp string (optional) # Output: stdout: timestamp (ISO-8601, no separators) # Depends: yell local argTime timeCurrent timeInput timeCurrentShort argTime="$1"; # Get Current Time timeCurrent="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. # Decide to parse current or supplied date ## Check if time argument empty if [[ -z "$argTime" ]]; then ## T: Time argument empty, use current time timeInput="$timeCurrent"; else ## F: Time argument exists, validate time if date --date="$argTime" 1>/dev/null 2>&1; then ### T: Time argument is valid; use it timeInput="$argTime"; else ### F: Time argument not valid; exit yell "ERROR:Invalid time argument supplied. Exiting."; exit 1; fi fi # Construct and deliver separator-les date string timeCurrentShort="$(date -d "$timeInput" +%Y%m%dT%H%M%S%z)"; echo "$timeCurrentShort"; } # Get YYYYmmddTHHMMSS±zzzz dateShort(){ # Desc: Date without separators (YYYYmmdd) # Usage: dateShort ([str date]) # Version: 1.1.1 # Input: arg1: 'date'-parsable timestamp string (optional) # Output: stdout: date (ISO-8601, no separators) # Depends: yell local argTime timeCurrent timeInput dateCurrentShort argTime="$1"; # Get Current Time timeCurrent="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second. # Decide to parse current or supplied date ## Check if time argument empty if [[ -z "$argTime" ]]; then ## T: Time argument empty, use current time timeInput="$timeCurrent"; else ## F: Time argument exists, validate time if date --date="$argTime" 1>/dev/null 2>&1; then ### T: Time argument is valid; use it timeInput="$argTime"; else ### F: Time argument not valid; exit yell "ERROR:Invalid time argument supplied. Exiting."; exit 1; fi fi # Construct and deliver separator-les date string dateCurrentShort="$(date -d "$timeInput" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day. echo "$dateCurrentShort"; } # Get YYYYmmdd timeDuration(){ # Desc: Given seconds, output ISO-8601 duration string # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2) # Usage: timeDuration [1:seconds] ([2:precision]) # Version: 1.0.4 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601) # arg2: precision level (optional; default=2) # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S") # exit code 0: success # exit code 1: error_input # exit code 2: error_unknown # Example: 'timeDuration 111111 3' yields 'P1DT6H51M' # Depends: date 8, bash 5, yell, local argSeconds argPrecision precision returnState remainder local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds local witherPrecision output local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds argSeconds="$1"; # read arg1 (seconds) argPrecision="$2"; # read arg2 (precision) precision=2; # set default precision # Check that between one and two arguments is supplied if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then yell "ERROR:Invalid number of arguments:$# . Exiting."; returnState="error_input"; fi # Check that argSeconds provided if [[ $# -ge 1 ]]; then ## Check that argSeconds is a positive integer if [[ "$argSeconds" =~ ^[[:digit:]]+$ ]]; then : else yell "ERROR:argSeconds not a digit."; returnState="error_input"; fi else yell "ERROR:No argument provided. Exiting."; exit 1; fi # Consider whether argPrecision was provided if [[ $# -eq 2 ]]; then # Check that argPrecision is a positive integer if [[ "$argPrecision" =~ ^[[:digit:]]+$ ]] && [[ "$argPrecision" -gt 0 ]]; then precision="$argPrecision"; else yell "ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early."; returnState="error_input"; fi; else : fi; remainder="$argSeconds" ; # seconds ## Calculate full years Y, update remainder fullYears=$(( remainder / (365*24*60*60) )); remainder=$(( remainder - (fullYears*365*24*60*60) )); ## Calculate full months M, update remainder fullMonths=$(( remainder / (30*24*60*60) )); remainder=$(( remainder - (fullMonths*30*24*60*60) )); ## Calculate full days D, update remainder fullDays=$(( remainder / (24*60*60) )); remainder=$(( remainder - (fullDays*24*60*60) )); ## Calculate full hours H, update remainder fullHours=$(( remainder / (60*60) )); remainder=$(( remainder - (fullHours*60*60) )); ## Calculate full minutes M, update remainder fullMinutes=$(( remainder / (60) )); remainder=$(( remainder - (fullMinutes*60) )); ## Calculate full seconds S, update remainder fullSeconds=$(( remainder / (1) )); remainder=$(( remainder - (remainder*1) )); ## Check which fields filled if [[ $fullYears -gt 0 ]]; then hasYears="true"; else hasYears="false"; fi if [[ $fullMonths -gt 0 ]]; then hasMonths="true"; else hasMonths="false"; fi if [[ $fullDays -gt 0 ]]; then hasDays="true"; else hasDays="false"; fi if [[ $fullHours -gt 0 ]]; then hasHours="true"; else hasHours="false"; fi if [[ $fullMinutes -gt 0 ]]; then hasMinutes="true"; else hasMinutes="false"; fi if [[ $fullSeconds -gt 0 ]]; then hasSeconds="true"; else hasSeconds="false"; fi ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2) witherPrecision="false" ### Years if $hasYears && [[ $precision -gt 0 ]]; then displayYears="true"; witherPrecision="true"; else displayYears="false"; fi; if $witherPrecision; then ((precision--)); fi; ### Months if $hasMonths && [[ $precision -gt 0 ]]; then displayMonths="true"; witherPrecision="true"; else displayMonths="false"; fi; if $witherPrecision && [[ $precision -gt 0 ]]; then displayMonths="true"; fi; if $witherPrecision; then ((precision--)); fi; ### Days if $hasDays && [[ $precision -gt 0 ]]; then displayDays="true"; witherPrecision="true"; else displayDays="false"; fi; if $witherPrecision && [[ $precision -gt 0 ]]; then displayDays="true"; fi; if $witherPrecision; then ((precision--)); fi; ### Hours if $hasHours && [[ $precision -gt 0 ]]; then displayHours="true"; witherPrecision="true"; else displayHours="false"; fi; if $witherPrecision && [[ $precision -gt 0 ]]; then displayHours="true"; fi; if $witherPrecision; then ((precision--)); fi; ### Minutes if $hasMinutes && [[ $precision -gt 0 ]]; then displayMinutes="true"; witherPrecision="true"; else displayMinutes="false"; fi; if $witherPrecision && [[ $precision -gt 0 ]]; then displayMinutes="true"; fi; if $witherPrecision; then ((precision--)); fi; ### Seconds if $hasSeconds && [[ $precision -gt 0 ]]; then displaySeconds="true"; witherPrecision="true"; else displaySeconds="false"; fi; if $witherPrecision && [[ $precision -gt 0 ]]; then displaySeconds="true"; fi; if $witherPrecision; then ((precision--)); fi; ## Determine whether or not the "T" separator is needed to separate date and time elements if ( $displayHours || $displayMinutes || $displaySeconds); then displayDateTime="true"; else displayDateTime="false"; fi ## Construct duration output string output="P" if $displayYears; then output=$output$fullYears"Y"; fi if $displayMonths; then output=$output$fullMonths"M"; fi if $displayDays; then output=$output$fullDays"D"; fi if $displayDateTime; then output=$output"T"; fi if $displayHours; then output=$output$fullHours"H"; fi if $displayMinutes; then output=$output$fullMinutes"M"; fi if $displaySeconds; then output=$output$fullSeconds"S"; fi ## Output duration string to stdout echo "$output" && returnState="true"; #===Determine function return code=== if [ "$returnState" = "true" ]; then return 0; elif [ "$returnState" = "error_input" ]; then yell "ERROR:input"; return 1; else yell "ERROR:Unknown"; return 2; fi } # Get duration (ex: PT10M4S ) displayMissing() { # Desc: Displays missing apps, files, and dirs # Usage: displayMissing # Version 0.1.0 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall # Output: stderr: messages indicating missing apps, file, or dirs # Depends: bash 5, checkAppFileDir() #==BEGIN Display errors== #===BEGIN Display Missing Apps=== missingApps="Missing apps :" #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done for key in "${!appRollCall[@]}"; do value="${appRollCall[$key]}" if [ "$value" = "false" ]; then #echo "DEBUG:Missing apps: $key => $value"; missingApps="$missingApps""$key " appMissing="true" fi done if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing. echo "$missingApps" 1>&2; fi #===END Display Missing Apps=== #===BEGIN Display Missing Files=== missingFiles="Missing files:" #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done for key in "${!fileRollCall[@]}"; do value="${fileRollCall[$key]}" if [ "$value" = "false" ]; then #echo "DEBUG:Missing files: $key => $value"; missingFiles="$missingFiles""$key " fileMissing="true" fi done if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing. echo "$missingFiles" 1>&2; fi #===END Display Missing Files=== #===BEGIN Display Missing Directories=== missingDirs="Missing dirs:" #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done for key in "${!dirRollCall[@]}"; do value="${dirRollCall[$key]}" if [ "$value" = "false" ]; then #echo "DEBUG:Missing dirs: $key => $value"; missingDirs="$missingDirs""$key " dirMissing="true" fi done if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing. echo "$missingDirs" 1>&2; fi #===END Display Missing Directories=== #==END Display errors== } # Display missing apps, files, dirs magicSetScriptTTL() { #Desc: Sets script_TTL seconds from provided time_element string argument #Usage: magicSetScriptTTL [str time_element] #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour") #Output: var: scriptTTL (integer seconds) #Depends: timeUntilNextHour, timeUntilNextDay local argTimeElement argTimeElement="$1" if [[ "$argTimeElement" = "day" ]]; then # Set script lifespan to end at start of next day if ! scriptTTL="$(timeUntilNextDay)"; then if [[ "$scriptTTL" -eq 0 ]]; then ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout. else yell "ERROR: timeUntilNextDay exit code $?"; exit 1; fi; fi; elif [[ "$argTimeElement" = "hour" ]]; then # Set script lifespan to end at start of next hour if ! scriptTTL="$(timeUntilNextHour)"; then if [[ "$scriptTTL" -eq 0 ]]; then ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout. else yell "ERROR: timeUntilNextHour exit code $?"; exit 1; fi; fi; else yell "ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1; fi } # Seconds until next (day|hour). checkMakeTar() { # Desc: Checks that a valid tar archive exists, creates one otherwise # Usage: checkMakeTar [ path ] # Version: 1.0.2 # Input: arg1: path of tar archive # Output: exit code 0 : tar readable # exit code 1 : tar missing; created # exit code 2 : tar not readable; moved; replaced # Depends: bash 5, date 8, tar 1, try() local pathTar returnFlag0 returnFlag1 returnFlag2 pathTar="$1"; # Check if file is a valid tar archive if tar --list --file="$pathTar" 1>/dev/null 2>&1; then ## T1: return success returnFlag0="tar valid"; else ## F1: Check if file exists if [[ -f "$pathTar" ]]; then ### T: Rename file try mv "$pathTar" "$pathTar""--broken--""$(date +%Y%m%dT%H%M%S)" && \ returnFlag1="tar moved"; else ### F: - : fi; ## F2: Create tar archive, return 0 try tar --create --file="$pathTar" --files-from=/dev/null && \ returnFlag2="tar created"; fi; # Determine function return code if [[ "$returnFlag0" = "tar valid" ]]; then return 0; elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then return 1; # tar missing so created elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then return 2; # tar not readable so moved; replaced fi; } # checks if arg1 is tar; creates one otherwise appendArgTar(){ # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) # Version: 1.0.6 # Input: arg1: data to be written # arg2: file name of file to be inserted into tar # arg3: tar archive path (must exist first) # arg4: temporary working dir # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") # Output: file written to disk # Example: decrypt multiple large files in parallel # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & # Depends: bash 5, tar 1, yell() # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533 local fn fileName tarPath tmpDir cmd0 cmd1 cmd2 cmd3 cmd4 # Save function name fn="${FUNCNAME[0]}"; #yell "DEBUG:STATUS:$fn:Finished appendArgTar()." # Set file name if ! [ -z "$2" ]; then fileName="$2"; else yell "ERROR:$fn:Not enough arguments."; exit 1; fi # Check tar path is a file if [ -f "$3" ]; then tarPath="$3"; else yell "ERROR:$fn:Tar archive arg not a file."; exit 1; fi # Check temp dir arg if ! [ -z "$4" ]; then tmpDir="$4"; else yell "ERROR:$fn:No temporary working dir set."; exit 1; fi # Set command strings if ! [ -z "$5" ]; then cmd1="$5"; else cmd1="cat "; fi # command string 1 if ! [ -z "$6" ]; then cmd2="$6"; else cmd2="cat "; fi # command string 2 if ! [ -z "$7" ]; then cmd3="$7"; else cmd3="cat "; fi # command string 3 if ! [ -z "$8" ]; then cmd4="$8"; else cmd4="cat "; fi # command string 4 # Input command cmd0="echo \"\$1\"" # # Debug # yell "DEBUG:STATUS:$fn:cmd0:$cmd0" # yell "DEBUG:STATUS:$fn:cmd1:$cmd1" # yell "DEBUG:STATUS:$fn:cmd2:$cmd2" # yell "DEBUG:STATUS:$fn:cmd3:$cmd3" # yell "DEBUG:STATUS:$fn:cmd4:$cmd4" # yell "DEBUG:STATUS:$fn:fileName:$fileName" # yell "DEBUG:STATUS:$fn:tarPath:$tarPath" # yell "DEBUG:STATUS:$fn:tmpDir:$tmpDir" # Write to temporary working dir eval "$cmd0 | $cmd1 | $cmd2 | $cmd3 | $cmd4" > "$tmpDir"/"$fileName"; # Append to tar try tar --append --directory="$tmpDir" --file="$tarPath" "$fileName"; #yell "DEBUG:STATUS:$fn:Finished appendArgTar()." } # Append Bash var to file appended to Tar archive appendFileTar(){ # Desc: Processes first file and then appends to tar # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...) # Version: 1.0.3 # Input: arg1: path of file to be (processed and) written # arg2: name to use for file inserted into tar # arg3: tar archive path (must exist first) # arg4: temporary working dir # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ") # Output: file written to disk # Example: decrypt multiple large files in parallel # appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" & # appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" & # appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" & # Depends: bash 5, tar 1, yell() local fn fileName tarPath tmpDir # Save function name fn="${FUNCNAME[0]}"; #yell "DEBUG:STATUS:$fn:Finished appendFileTar()." # Set file name if ! [ -z "$2" ]; then fileName="$2"; else yell "ERROR:$fn:Not enough arguments."; exit 1; fi # Check tar path is a file if [ -f "$3" ]; then tarPath="$3"; else yell "ERROR:$fn:Tar archive arg not a file:$3"; exit 1; fi # Check temp dir arg if ! [ -z "$4" ]; then tmpDir="$4"; else yell "ERROR:$fn:No temporary working dir set."; exit 1; fi # Set command strings if ! [ -z "$5" ]; then cmd1="$5"; else cmd1="cat "; fi # command string 1 if ! [ -z "$6" ]; then cmd2="$6"; else cmd2="cat "; fi # command string 2 if ! [ -z "$7" ]; then cmd3="$7"; else cmd3="cat "; fi # command string 3 if ! [ -z "$8" ]; then cmd4="$8"; else cmd4="cat "; fi # command string 4 # Input command string cmd0="cat \"\$1\"" # # Debug # yell "DEBUG:STATUS:$fn:cmd0:$cmd0" # yell "DEBUG:STATUS:$fn:cmd1:$cmd1" # yell "DEBUG:STATUS:$fn:cmd2:$cmd2" # yell "DEBUG:STATUS:$fn:cmd3:$cmd3" # yell "DEBUG:STATUS:$fn:cmd4:$cmd4" # yell "DEBUG:STATUS:$fn:fileName:$fileName" # yell "DEBUG:STATUS:$fn:tarPath:$tarPath" # yell "DEBUG:STATUS:$fn:tmpDir:$tmpDir" # Write to temporary working dir eval "$cmd0 | $cmd1 | $cmd2 | $cmd3 | $cmd4" > "$tmpDir"/"$fileName"; # Append to tar try tar --append --directory="$tmpDir" --file="$tarPath" "$fileName"; #yell "DEBUG:STATUS:$fn:Finished appendFileTar()." } # Append file to Tar archive checkAgePubkey() { # Desc: Checks if string is an age-compatible pubkey # Usage: checkAgePubkey [str pubkey] # Version: 0.1.2 # Input: arg1: string # Output: return code 0: string is age-compatible pubkey # return code 1: string is NOT an age-compatible pubkey # age stderr (ex: there is stderr if invalid string provided) # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 ) argPubkey="$1"; if echo "test" | age -a -r "$argPubkey" 1>/dev/null; then return 0; else return 1; fi; } # Check age pubkey validateInput() { # Desc: Validates Input # Usage: validateInput [str input] [str input type] # Version: 0.3.1 # Input: arg1: string to validate # arg2: string specifying input type (ex:"ssh_pubkey") # Output: return code 0: if input string matched specified string type # Depends: bash 5, yell() local fn argInput argType # Save function name fn="${FUNCNAME[0]}"; # Process arguments argInput="$1"; argType="$2"; if [[ $# -gt 2 ]]; then yell "ERROR:$0:$fn:Too many arguments."; exit 1; fi; # Check for blank if [[ -z "$argInput" ]]; then return 1; fi # Define input types ## ssh_pubkey ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA") if [[ "$argType" = "ssh_pubkey" ]]; then if [[ "$argInput" =~ ^[[:alnum:]-]*[\ ]*[[:alnum:]+/=]*$ ]]; then return 0; fi; fi; ## age_pubkey ### Check for age1[:bech32:] if [[ "$argType" = "age_pubkey" ]]; then if [[ "$argInput" =~ ^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*$ ]]; then return 0; fi; fi ## integer if [[ "$argType" = "integer" ]]; then if [[ "$argInput" =~ ^[[:digit:]]*$ ]]; then return 0; fi; fi; ## time element (year, month, week, day, hour, minute, second) if [[ "$argType" = "time_element" ]]; then if [[ "$argInput" = "year" ]] || \ [[ "$argInput" = "month" ]] || \ [[ "$argInput" = "week" ]] || \ [[ "$argInput" = "day" ]] || \ [[ "$argInput" = "hour" ]] || \ [[ "$argInput" = "minute" ]] || \ [[ "$argInput" = "second" ]]; then return 0; fi; fi; # Return error if no condition matched. return 1; } # Validates strings timeEpochNS() { # Desc: Get epoch nanoseconds # Usage: timeEpochNS # Version 0.2.3 # Input: arg1: 'date'-parsable timestamp string (optional) # Output: Nanoseconds since 1970-01-01 # Depends: date 8, cut 8, yell() # Ref/Attrib: Force base 10 Bash arith with '10#'. https://stackoverflow.com/a/24777667 local argTime timeCurrent timeInput timeEpochFloat timeEpochInt local timeEpochNsFrac timeEpochNs argTime="$1"; # Get Current Time timeCurrent="$(date --iso-8601=ns)"; # Produce `date`-parsable current timestamp with resolution of 1 nanosecond. # Decide to parse current or supplied time ## Check if time argument empty if [[ -z "$argTime" ]]; then ## T: Time argument empty, use current time timeInput="$timeCurrent"; else ## F: Time argument exists, validate time if date --date="$argTime" 1>/dev/null 2>&1; then ### T: Time argument is valid; use it timeInput="$argTime"; else ### F: Time argument not valid; exit yell "ERROR:Invalid time argument supplied. Exiting."; exit 1; fi; fi; # Construct and deliver nanoseconds since 1970-01-01 timeEpochFloat="$(date --date="$timeInput" +%s.%N)"; # Save ssss.NNNNNNNNN timeEpochInt="$(echo "$timeEpochFloat" | cut -d. -f1)"; # Get ssss timeEpochNsFrac="$(echo "$timeEpochFloat" | cut -d. -f2)"; # Get NNNNNNNNN timeEpochNs="$(( (10#"$timeEpochInt" * 10**9) + (10#"$timeEpochNsFrac") ))"; echo "$timeEpochNs"; } # Nanoseconds since 1970-01-01 magicBufferSleepPID() { # Desc: Compensates for lag so buffer rounds start every bufferTTL seconds # Input: vars: bufferTTL, errReset # # Input: array: errorHistory errResetx10e3 # Output: vars: bufferTTL_AdjFloat # Re/Attrib: https://en.wikipedia.org/wiki/PID_controller#Standard_versus_parallel_(ideal)_form local buffer_ttl_ns k_p t_i t_d local timeBufferStartNS timeBufferStartNSExp errNS errReset errRate local adj buffer_ttl_adj_ns buffer_ttl_adj_int buffer_ttl_adj_floatfrac # local errNSx10e3 errResetx10e3 errRatex10e3 # local errorHistorySize # ## Define errorHistorySize # errorHistorySize=100; ## Define bufferTTL in nanoseconds buffer_ttl_ns=$((bufferTTL * 10**9)) && vbm "buffer_ttl_ns:$buffer_ttl_ns"; ### PID Control factors k_p=1; # Gain for compensating buffer round lag t_i="$(((4)*buffer_ttl_ns/(1)))"; # Consider this number of past nanoseconds to eliminate error t_d="$(((1)*buffer_ttl_ns/(1)))"; # Predict value this number of nanoseconds into the future # Calculate Error, errNS, in nanoseconds ## Get current time timeBufferStartNS="$(timeEpochNS)" && vbm "timeBufferStartNS :$timeBufferStartNS"; ## Calculate expected time (from start time, current buffer round number, nominal bufferTTL) timeBufferStartNSExp="$(( (timeBufferFirstNS) + (buffer_ttl_ns * bufferRound) ))" && vbm "timeBufferStartNSExp:$timeBufferStartNSExp"; ## Calculate error (diff between timeBufferStartNSExp and timeBufferStartNS; usually negative) errNS="$(( timeBufferStartNSExp - timeBufferStartNS ))" && vbm "errNS:$errNS"; # errNSx10e3="$((errNS*10**3))" && vbm "errNSx10e3:$errNSx10e3"; # ## Append error to errorHistory # errorHistory+=("errNS"); # ### Trim errorHistory array if over errorHistorySize # while [[ "${#errorHistory[@]}" -gt "errorHistorySize" ]]; then do # unset "errorHistory[0]"; # remove oldest entry, creating sparse array # errorHistory=("${errorHistory[@]}"); # reindex sparse array # vbm "STATUS:Trimmed errorHistory array. Entry count:${#errorHistory[@]}"; # done; # Calculate errReset in nanoseconds^2 ## errReset = int(errHistory(t),wrt(delta_buffer_ttl)) ## Integrate errorHistory with respect to time # for value in "${errorHistory[@]}"; do # errReset=$(( errReset + ( value*buffer_ttl_ns ) )); # done; vbm "errReset(orig):$errReset" errReset="$(( (errReset + (errNS*buffer_ttl_ns)) ))" && vbm "errReset(post):$errReset"; # errResetx10e3="$(( ( errResetx10e3 + ( errNSx10e3 * buffer_ttl_ns ) )*10**3 ))" && vbm "errResetx10e3:$errResetx10e3"; # Calculate errRate in nanoseconds per nanosecond errRate="$(( errNS / buffer_ttl_ns ))" && vbm "errRate:$errRate"; # errRatex10e3="$(( ( errNSx10e3 ) / buffer_ttl_ns ))" && vbm "errRatex10e3:$errRatex10e3"; # Debug vbm "errNS :$errNS"; vbm "errResetTerm:$((errReset/t_i))"; vbm "errRateTerm :$((errRate*t_d))"; # Calculate PID control signal ## adj = k_p * (errNS + errReset/t_i + errRate*t_d) adj="$(( k_p*(errNS + errReset/t_i + errRate*t_d) ))" && vbm "adj:$adj"; # adj="$((k_p*(errNSx10e3 + (errResetx10e3/t_i) + (errRatex10e3*t_d) )/(10**3)))" && vbm "adj:$adj"; # Calculate bufferTTL_AdjFloat from adj (ns) ## Calculate buffer_ttl_adj in nanoseconds (buffer_ttl_adj_ns = buffer_ttl_ns + adj) buffer_ttl_adj_ns="$((buffer_ttl_ns + adj))" && vbm "buffer_ttl_adj_ns:$buffer_ttl_adj_ns"; ## Calculate integer seconds buffer_ttl_adj_int="$((buffer_ttl_adj_ns/(10**9)))" && vbm "buffer_ttl_adj_int:$buffer_ttl_adj_int"; ### Catch negative integer seconds, set minimum of bufferTTL/10 seconds if [[ "$buffer_ttl_adj_int" -le "$((bufferTTL/10))" ]]; then buffer_ttl_adj_int="$((bufferTTL/10))"; yell "WARNING:Buffer lag adjustment yielded negative seconds."; fi; ## Calculate nanosecond remainder ### Remove integer buffer_ttl_adj_floatfrac="$((buffer_ttl_adj_ns - (buffer_ttl_adj_int*(10**9)) ))" && vbm "buffer_ttl_adj_floatfrac:$buffer_ttl_adj_floatfrac"; ### Calc absolute value of fraction (by removing '-' if present; see https://stackoverflow.com/a/47240327 buffer_ttl_adj_floatfrac="${buffer_ttl_adj_floatfrac#-}" && vbm "buffer_ttl_adj_floatfrac:$buffer_ttl_adj_floatfrac"; ## Form float bufferTTL_AdjFloat (function output) bufferTTL_AdjFloat="$buffer_ttl_adj_int"."$buffer_ttl_adj_floatfrac" && vbm "bufferTTL_AdjFloat:$bufferTTL_AdjFloat"; vbm "STATUS:Calculated adjusted bufferTTL (seconds):$bufferTTL_AdjFloat"; } # Calc bufferTTL_AdjFloat so buffer starts every bufferTTL seconds magicWriteVersion() { # Desc: Appends time-stamped VERSION to pathout_tar # Usage: magicWriteVersion # Input: vars: pathout_tar, dir_tmp # Input: array: recPubKeysValid # Input: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname # Output: appends tar (pathout_tar) # Depends: dateTimeShort, appendArgTar local fileoutVersion contentVersion pubKeyIndex # Set VERSION file name fileoutVersion="$(dateTimeShort)..VERSION"; # Gather VERSION data in contentVersion contentVersion="scriptVersion=$scriptVersion"; #contentVersion="$contentVersion""\\n"; contentVersion="$contentVersion""\\n""scriptName=$scriptName"; contentVersion="$contentVersion""\\n""scriptURL=$scriptURL"; contentVersion="$contentVersion""\\n""ageVersion=$ageVersion"; contentVersion="$contentVersion""\\n""ageURL=$ageURL"; contentVersion="$contentVersion""\\n""date=$(date --iso-8601=seconds)"; contentVersion="$contentVersion""\\n""hostname=$scriptHostname"; ## Add list of recipient pubkeys for pubkey in "${recPubKeysValid[@]}"; do ((pubKeyIndex++)) contentVersion="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey"; done ## Process newline escapes contentVersion="$(echo -e "$contentVersion")" # Write contentVersion as file fileoutVersion and write-append to pathout_tar appendArgTar "$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp"; } # bkgpslog: write version data to pathout_tar via appendArgTar() magicGatherWriteBuffer() { # Desc: bkgpslog-specific meta function for writing data to dir_tmp then appending each file to pathout_tar # Inputs: vars: pathout_tar, cmd_conv_{nmea,gpx,kml}, cmd_{compress,encrypt} dir_tmp, # Inputs: vars: bufferTTL bufferTTL_STR scriptHostname cmd_compress_suffix cmd_encrypt_suffix # Output: file: (pathout_tar) # Depends: tar 1, date 8, sleep 8, yell(), try(), vbm(), appendArgTar(), checkMakeTar() # Depends: magicWriteVersion(), appendFileTar() local fn pathoutBuffer timeBufferStartLong timeBufferStart fileoutBasename local fileout_nmea fileout_gpx fileout_kml pathout_nmea pathout_gpx pathout_kml pathout_tar # Debug:Get function name fn="${FUNCNAME[0]}"; vbm "DEBUG:STATUS:$fn:Started magicGatherWriteBuffer()."; # Create buffer file with unique name pathoutBuffer="$dir_tmp/buffer$SECONDS" && vbm "pathoutBuffer:$pathoutBuffer"; # Fill buffer timeout "$bufferTTL"s gpspipe -r -o "$pathoutBuffer" ; timeBufferStartLong="$(date --date="$bufferTTL seconds ago" --iso-8601=seconds)" && vbm "timeBufferStartLong:$timeBufferStartLong"; timeBufferStart="$(dateTimeShort "$timeBufferStartLong" )" && vbm "timeBufferStart:$timeBufferStart"; # Note start time # Determine file paths (time is start of buffer period) fileoutBasename="$timeBufferStart""--""$bufferTTL_STR""..""$scriptHostname""_location" && vbm "STATUS:Set fileoutBasename to:$fileoutBasename"; ## Files saved to dir_tmp fileout_nmea="$fileoutBasename".nmea"$cmd_compress_suffix""$cmd_encrypt_suffix" && vbm "STATUS:Set fileout_nmea to:$fileout_nmea"; fileout_gpx="$fileoutBasename".gpx"$cmd_compress_suffix""$cmd_encrypt_suffix" && vbm "STATUS:Set fileout_gpx to:$fileout_gpx"; fileout_kml="$fileoutBasename".kml"$cmd_compress_suffix""$cmd_encrypt_suffix" && vbm "STATUS:Set fileout_kml to:$fileout_kml"; pathout_nmea="$dir_tmp"/"$fileout_nmea" && vbm "STATUS:Set pathout_nmea to:$pathout_nmea"; pathout_gpx="$dir_tmp"/"$fileout_gpx" && vbm "STATUS:Set pathout_gpx to:$pathout_gpx"; pathout_kml="$dir_tmp"/"$fileout_kml" && vbm "STATUS:Set pathout_kml to:$pathout_kml"; ## Files saved to disk (dirOut) ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar") pathout_tar="$dirOut"/"$(dateShort "$(date --date="$bufferTTL seconds ago" --iso-8601=seconds)")".."$scriptHostname""_location""$cmd_compress_suffix""$cmd_encrypt_suffix".tar && \ vbm "STATUS:Set pathout_tar to:$pathout_tar"; # DEBUG: check vars vbm "STATUS:fn :$fn"; vbm "STATUS:dir_tmp :$dir_tmp"; vbm "STATUS:pathout_tar :$pathout_tar"; vbm "STATUS:pathout_nmea :$pathout_nmea"; vbm "STATUS:pathout_gpx :$pathout_gpx"; vbm "STATUS:pathout_kml :$pathout_kml"; vbm "STATUS:bufferTTL :$bufferTTL"; vbm "STATUS:pathoutBuffer :$pathoutBuffer"; vbm "STATUS:timeBufferStart:$timeBufferStart"; vbm "fileoutBasename :$fileoutBasename"; # Validate pathout_tar as tar. checkMakeTar "$pathout_tar"; ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2) vbm "exit status before magicWriteVersion:$?" if [[ $? -eq 1 ]] || [[ $? -eq 2 ]]; then magicWriteVersion; fi # Write bufferBash to pathout_tar wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html ) appendFileTar "$pathoutBuffer" "$fileout_nmea" "$pathout_tar" "$dir_tmp" "$cmd_conv_nmea" "$cmd_compress" "$cmd_encrypt"; # Write NMEA data appendFileTar "$pathoutBuffer" "$fileout_gpx" "$pathout_tar" "$dir_tmp" "$cmd_conv_gpx" "$cmd_compress" "$cmd_encrypt"; # Write GPX file appendFileTar "$pathoutBuffer" "$fileout_kml" "$pathout_tar" "$dir_tmp" "$cmd_conv_kml" "$cmd_compress" "$cmd_encrypt"; # Write KML file # Remove secured chunks from dir_tmp rm "$pathoutBuffer" "$pathout_nmea" "$pathout_gpx" "$pathout_kml"; vbm "DEBUG:STATUS:$fn:Finished magicGatherWriteBuffer()."; } # write buffer to disk magicParseRecipientDir() { # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory") # Inputs: vars: optionRecDir, argRecDir, optionEncrypt # arry: recPubKeysValid # Outputs: arry: recPubKeysValid # Depends: processArguments, local recipientDir recFileLine updateRecipients declare -a candRecPubKeysValid # Check that '-e' and '-R' set if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then ### Check that argRecDir is a directory. if [[ -d "$argRecDir" ]]; then recipientDir="$argRecDir" && vbm "STATUS:Recipient watch directory detected:\"$recipientDir\""; #### Initialize variable indicating outcome of pubkey review unset updateRecipients #### Add existing recipients candRecPubKeysValid=("${recPubKeysValidStatic[@]}"); #### Parse files in recipientDir for file in "$recipientDir"/*; do ##### Read first line of each file recFileLine="$(head -n1 "$file")" && vbm "STATUS:Checking if pubkey:\"$recFileLine\""; ##### check if first line is a valid pubkey if checkAgePubkey "$recFileLine" && \ ( validateInput "$recFileLine" "ssh_pubkey" || validateInput "$recFileLine" "age_pubkey"); then ###### T: add candidate pubkey to candRecPubKeysValid candRecPubKeysValid+=("$recFileLine") && vbm "STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\""; else ###### F: throw warning; yell "ERROR:Invalid recipient file detected. Not modifying recipient list." updateRecipients="false"; fi; done #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected if ! [[ "$updateRecipients" = "false" ]]; then recPubKeysValid=("${candRecPubKeysValid[@]}") && vbm "STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\""; fi; else yell "ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1; fi; fi; # Handle case if '-R' set but '-e' not set if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then yell "ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi; } # Update recPubKeysValid with argRecDir magicParseRecipientArgs() { # Desc: Parses recipient arguments specified by '-r' option # Input: vars: optionEncrypt, optionRecipients # arry: argRecPubKeys from processArguments() # Output: vars: cmd_encrypt, cmd_encrypt_suffix # arry: recPubKeysValid, recPubKeysValidStatic # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments() local recipients # Check if encryption option active. if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then if checkapp age; then # Check that age is available. for pubkey in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message vbm "DEBUG:Testing pubkey string:$pubkey"; if checkAgePubkey "$pubkey" && \ ( validateInput "$pubkey" "ssh_pubkey" || validateInput "$pubkey" "age_pubkey"); then #### Form age recipient string recipients="$recipients""-r '$pubkey' "; vbm "STATUS:Added pubkey for forming age recipient string:""$pubkey"; vbm "DEBUG:recipients:""$recipients"; #### Add validated pubkey to recPubKeysValid array recPubKeysValid+=("$pubkey") && vbm "DEBUG:recPubkeysValid:pubkey added:$pubkey"; else yell "ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1; fi; done vbm "DEBUG:Finished processing argRecPubKeys array"; vbm "STATUS:Array of validated pubkeys:${recPubKeysValid[*]}"; recPubKeysValidStatic=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function ## Form age command string cmd_encrypt="age ""$recipients " && vbm "cmd_encrypt:$cmd_encrypt"; cmd_encrypt_suffix=".age" && vbm "cmd_encrypt_suffix:$cmd_encrypt_suffix"; else yell "ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1; fi; else cmd_encrypt="tee /dev/null " && vbm "cmd_encrypt:$cmd_encrypt"; cmd_encrypt_suffix="" && vbm "cmd_encrypt_suffix:$cmd_encrypt_suffix"; vbm "DEBUG:Encryption not enabled." fi; # Catch case if '-e' is set but '-r' or '-R' is not if [[ "$optionEncrypt" = "true" ]] && [[ ! "$optionRecipients" = "true" ]]; then yell "ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi; # Catch case if '-r' or '-R' set but '-e' is not if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then yell "ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi; } # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix magicParseCompressionArg() { # Desc: Parses compression arguments specified by '-c' option # Input: vars: optionCompress # Output: cmd_compress, cmd_compress_suffix # Depends: checkapp(), vbm(), gzip, if [[ "$optionCompress" = "true" ]]; then # Check if compression option active if checkapp gzip; then # Check if gzip available cmd_compress="gzip " && vbm "cmd_compress:$cmd_compress"; cmd_compress_suffix=".gz" && vbm "cmd_compress_suffix:$cmd_compress_suffix"; else yell "ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1; fi else cmd_compress="tee /dev/null " && vbm "cmd_compress:$cmd_compress"; cmd_compress_suffix="" && vbm "cmd_compress_suffix:$cmd_compress_suffix"; vbm "DEBUG:Compression not enabled."; fi } # Form compression cmd string and filename suffix magicInitWorkingDir() { # Desc: Determine temporary working directory from defaults or user input # Usage: magicInitWorkingDir # Input: vars: optionTmpDir, argTempDirPriority, dirTmpDefault # Input: vars: scriptTimeStart # Output: vars: dir_tmp # Depends: processArguments(), vbm(), yell() # Parse '-t' option (user-specified temporary working dir) ## Set dir_tmp_parent to user-specified value if specified local dir_tmp_parent if [[ "$optionTmpDir" = "true" ]]; then if [[ -d "$argTempDirPriority" ]]; then dir_tmp_parent="$argTempDirPriority"; else yell "WARNING:Specified temporary working directory not valid:$argTempDirPriority"; exit 1; # Exit since user requires a specific temp dir and it is not available. fi; else ## Set dir_tmp_parent to default or fallback otherwise if [[ -d "$dirTmpDefault" ]]; then dir_tmp_parent="$dirTmpDefault"; elif [[ -d /tmp ]]; then yell "WARNING:$dirTmpDefault not available. Falling back to /tmp ."; dir_tmp_parent="/tmp"; else yell "ERROR:No valid working directory available. Exiting."; exit 1; fi fi; ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart) dir_tmp="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm "DEBUG:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main(). } # Sets working dir magicParseCustomTTL() { # Desc: Set user-specified TTLs for buffer and script # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string) # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE # Input: vars: bufferTTL (integer), scriptTTL_TE (string) # Output: bufferTTL (integer), scriptTTL_TE (string) # Depends validateInput(), showUsage(), yell # React to '-b, --buffer-ttl' option if [[ "$optionCustomBufferTTL" = "true" ]]; then ## T: Check if argCustomBufferTTL is an integer if validateInput "$argCustomBufferTTL" "integer"; then ### T: argCustomBufferTTL is an integer bufferTTL="$argCustomBufferTTL" && vbm "Custom bufferTTL from -b:$bufferTTL"; else ### F: argcustomBufferTTL is not an integer yell "ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage; exit 1; fi; ## F: do not change bufferTTL fi; # React to '-B, --script-ttl' option if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour") if validateInput "$argCustomScriptTTL" "time_element"; then ### T: argCustomScriptTTL is a time element scriptTTL_TE="$argCustomScriptTTL" && vbm "Custom scriptTTL_TE from -B:$scriptTTL_TE"; else ### F: argcustomScriptTTL is not a time element yell "ERROR:Invalid time element argument for custom script time-to-live."; showUsage; exit 1; fi; ## F: do not change scriptTTL_TE fi; } # Sets custom script or buffer TTL if specified main() { # DEBUG: Print environment variables vbm "echo $(printenv)"; # Process arguments processArguments "$@"; ## Act upon arguments ### Determine working directory magicInitWorkingDir; # Sets dir_tmp from argTempDirPriority ### Set output encryption and compression option strings #### React to "-r" ("encryption recipients") option magicParseRecipientArgs; # Updates recPubKeysValid, cmd_encrypt[_suffix] from argRecPubKeys #### React to "-c" ("compression") option magicParseCompressionArg; # Updates cmd_compress[_suffix] #### React to "-R" ("recipient directory") option magicParseRecipientDir; # Updates recPubKeysValid #### React to custom buffer and script TTL options ("-b", "-B") magicParseCustomTTL; # Sets custom scriptTTL_TE and/or bufferTTL if specified # Check that critical apps and dirs are available, display missing ones. if ! checkapp gpspipe tar && ! checkdir "$dirOut" "dir_tmp"; then yell "ERROR:Critical components missing."; displayMissing; yell "Exiting."; exit 1; fi # Set script lifespan (scriptTTL from scriptTTL_TE) magicSetScriptTTL "$scriptTTL_TE"; ## Note: scriptTTL_TE is time element string (ex: "day") while scriptTTL is integer seconds # File name substring (ISO-8601 duration from bufferTTL) bufferTTL_STR="$(timeDuration "$bufferTTL")" && vbm "DEBUG:bufferTTL_STR:$bufferTTL_STR"; # Init temp working dir try mkdir "$dir_tmp" && vbm "DEBUG:Working dir created at dir_tmp:$dir_tmp"; # Initialize 'tar' archive ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar")) pathout_tar="$dirOut"/"$(dateShort "$(date --date="$bufferTTL seconds ago" --iso-8601=seconds)")".."$scriptHostname""_location""$cmd_compress_suffix""$cmd_encrypt_suffix".tar && \ vbm "STATUS:Set pathout_tar to:$pathout_tar"; ## Check that pathout_tar is a tar. Rename old and create empty one otherwise. checkMakeTar "$pathout_tar" && vbm "DEBUG:Confirmed or Created to be a tar:$pathout_tar"; ## Append VERSION file to pathout_tar magicWriteVersion; # Define GPS conversion commands cmd_conv_nmea="tee /dev/null " && vbm "STATUS:Set cmd_conv_nmea to:$cmd_conv_nmea"; # tee as passthrough cmd_conv_gpx="gpsbabel -i nmea -f - -o gpx -F - " && vbm "STATUS:Set cmd_conv_gpx to:$cmd_conv_gpx"; # convert NMEA to GPX cmd_conv_kml="gpsbabel -i nmea -f - -o kml -F - " && vbm "STATUS:Set cmd_conv_kml to:$cmd_conv_kml"; # convert NMEA to KML # MAIN LOOP:Record gps data until script lifespan ends timeBufferFirstNS="$(timeEpochNS)"; bufferRound=0; bufferTTL_AdjFloat="$bufferTTL"; while [[ "$SECONDS" -lt "$scriptTTL" ]]; do if ! [[ -d "$dir_tmp" ]]; then yell "ERROR:dir_tmp existence failure:$dir_tmp"; try mkdir "$dir_tmp" && vbm "DEBUG:Working dir recreated dir_tmp:$dir_tmp"; fi magicParseRecipientDir; magicGatherWriteBuffer & sleep "$bufferTTL_AdjFloat"; # adjusted by magicBufferSleepPID ((bufferRound++)); magicBufferSleepPID; # Calculates bufferTTL_AdjFloat from bufferTTL given buffer expected start time vs. actual done # Cleanup ## Remove dir_tmp try rm -r "$dir_tmp" && vbm "Removed dir_tmp:$dir_tmp"; vbm "STATUS:Main function finished."; } # Main function. #===END Declare local script functions=== #==END Define script parameters== #==BEGIN Perform work and exit== main "$@" # Run main function. exit 0; #==END Perform work and exit== # Author: Steven Baltakatei Sandoval; # License: GPLv3+