fix(bkgpslog):Fix recPubKeysValid array bloat
[EVA-2020-02.git] / exec / bkgpslog
CommitLineData
032f4b05
SBS
1#!/bin/bash
2
8fbca23d
SBS
3# Desc: Records gps data until midnight
4# Author: Steven Baltakatei Sandoval; License: GPLv3+
aa2a49f1 5# Usage: bkgpslog -o [output dir]
032f4b05 6
8fbca23d 7#==BEGIN Define script parameters==
6c30388f 8## Logging Behavior parameters
3fd6a69e 9BUFFER_TTL="300"; # time between file writes
2af0d7d9 10SCRIPT_TTL_TE="day"; # (day|hour)
aa2a49f1
SBS
11#### TZ="UTC"; export TZ; # Default time zone; overridden by '--time-zone=[str]' option
12DIR_TMP_DEFAULT="/dev/shm"; # Default parent of working directory
6c30388f 13
aa2a49f1
SBS
14SCRIPT_TIME_START=$(date +%Y%m%dT%H%M%S.%N);
15PATH="$HOME/.local/bin:$PATH"; # Add "$(systemd-path user-binaries)" path in case apps saved there
16SCRIPT_HOSTNAME=$(hostname); # Save hostname of system running this script.
6010b172 17SCRIPT_VERSION="0.4.2"; # Define version of script.
ff22a93f
SBS
18SCRIPT_NAME="bkgpslog"; # Define basename of script file.
19SCRIPT_URL="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script.
20AGE_VERSION="1.0.0-beta2"; # Define version of age (encryption program)
21AGE_URL="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age.
8fbca23d
SBS
22
23declare -Ag appRollCall # Associative array for storing app status
24declare -Ag fileRollCall # Associative array for storing file status
25declare -Ag dirRollCall # Associative array for storing dir status
320ac29c 26declare -a argRecPubKeys # for processArguments function
8fbca23d 27
cfc25c90
SBS
28## Initialize variables
29OPTION_VERBOSE=""; OPTION_ENCRYPT=""; OPTION_COMPRESS=""; OPTION_TMPDIR="";
30
8fbca23d
SBS
31#===BEGIN Declare local script functions===
32checkapp() {
33 # Desc: If arg is a command, save result in assoc array 'appRollCall'
34 # Usage: checkapp arg1 arg2 arg3 ...
35 # Input: global assoc. array 'appRollCall'
36 # Output: adds/updates key(value) to global assoc array 'appRollCall'
37 local returnState
38 #echo "DEBUG:$(date +%S.%N)..Starting checkapp function."
39 #echo "DEBUG:args: $@"
40 #echo "DEBUG:returnState:$returnState"
41
42 #===Process Args===
43 for arg in "$@"; do
44 #echo "DEBUG:processing arg:$arg"
0b3dde05 45 if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
8fbca23d
SBS
46 appRollCall[$arg]="true";
47 #echo "DEBUG:appRollCall[$arg]:"${appRollCall[$arg]}
48 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
49 else
50 appRollCall[$arg]="false"; returnState="false";
51 fi
52 done
53
54 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
55 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
56
57 #===Determine function return code===
58 if [ "$returnState" = "true" ]; then
59 #echo "DEBUG:checkapp returns true for $arg";
60 return 0;
61 else
62 #echo "DEBUG:checkapp returns false for $arg";
63 return 1;
64 fi
65} # Check that app exists
66checkfile() {
67 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
68 # Usage: checkfile arg1 arg2 arg3 ...
69 # Input: global assoc. array 'fileRollCall'
70 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
71 # Output: returns 0 if app found, 1 otherwise
72 local returnState
73
74 #===Process Args===
75 for arg in "$@"; do
76 #echo "DEBUG:processing arg:$arg"
77 if [ -f "$arg" ]; then
78 fileRollCall["$arg"]="true";
79 #echo "DEBUG:fileRollCall[\"$arg\"]:"${fileRollCall["$arg"]}
80 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
81 else
82 fileRollCall["$arg"]="false"; returnState="false";
83 fi
84 done
85
86 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:fileRollCall key [$key] is:${fileRollCall[$key]}"; done
87 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
88
89 #===Determine function return code===
90 if [ "$returnState" = "true" ]; then
91 #echo "DEBUG:checkapp returns true for $arg";
92 return 0;
93 else
94 #echo "DEBUG:checkapp returns false for $arg";
95 return 1;
96 fi
97} # Check that file exists
98checkdir() {
99 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
100 # Usage: checkdir arg1 arg2 arg3 ...
101 # Input: global assoc. array 'dirRollCall'
102 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
103 # Output: returns 0 if app found, 1 otherwise
104 local returnState
105
106 #===Process Args===
107 for arg in "$@"; do
108 #echo "DEBUG:processing arg:$arg"
109 if [ -d "$arg" ]; then
110 dirRollCall["$arg"]="true";
111 #echo "DEBUG:dirRollCall[\"$arg\"]:"${dirRollCall["$arg"]}
112 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
bcf09dcc 113 elif [ "$arg" = "" ]; then
8fbca23d 114 dirRollCall["$arg"]="false"; returnState="false";
bcf09dcc
SBS
115 else
116 returnState="false";
8fbca23d
SBS
117 fi
118 done
119
120 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:dirRollCall key [$key] is:${dirRollCall[$key]}"; done
121 #echo "DEBUG:evaluating returnstate. returnState:"$returnState
122
123 #===Determine function return code===
124 if [ "$returnState" = "true" ]; then
125 #echo "DEBUG:checkapp returns true for $arg";
126 return 0;
127 else
128 #echo "DEBUG:checkapp returns false for $arg";
129 return 1;
130 fi
131} # Check that dir exists
032f4b05 132
c609b9c8
SBS
133# Yell, Die, Try Three-Fingered Claw technique
134# Ref/Attrib: https://stackoverflow.com/a/25515370
135yell() { echo "$0: $*" >&2; }
136die() { yell "$*"; exit 111; }
137try() { "$@" || die "cannot $*"; }
138
032f4b05
SBS
139echoerr() {
140 echo "$@" 1>&2; # Define stderr echo function.
141} # Define stderr message function.
142showUsage() {
143 echoerr "USAGE:"
94e094d1 144 echoerr " bkgpslog [ options ]"
032f4b05
SBS
145 echoerr
146 echoerr "OPTIONS:"
147 echoerr " -h, --help"
148 echoerr " Display help information."
032f4b05
SBS
149 echoerr " --version"
150 echoerr " Display script version."
032f4b05
SBS
151 echoerr " -v, --verbose"
152 echoerr " Display debugging info."
17d49005
SBS
153 echoerr " -e, --encrypt"
154 echoerr " Encrypt output."
2af0d7d9 155 echoerr " -r, --recipient [ string pubkey ]"
f7f33d33 156 echoerr " Specify recipient. May be age or ssh pubkey."
2af0d7d9 157 echoerr " May be specified multiple times for multiple pubkeys."
f7f33d33 158 echoerr " See https://github.com/FiloSottile/age"
2af0d7d9 159 echoerr " -o, --output [ path dir ]"
032f4b05 160 echoerr " Specify output directory to save logs."
408a342b
SBS
161 echoerr " -c, --compress"
162 echoerr " Compress output with gzip (before encryption if enabled)."
f7f33d33
SBS
163 echoerr " -z, --time-zone"
164 echoerr " Specify time zone. (ex: \"America/New_York\")"
2af0d7d9 165 echoerr " -t, --temp-dir [path dir]"
f7f33d33
SBS
166 echoerr " Specify parent directory for temporary working directory."
167 echoerr " Default: \"/dev/shm\""
2af0d7d9 168 echoerr " -R, --recipient-dir [path dir]"
320ac29c 169 echoerr " Specify directory containing files whose first lines are"
2af0d7d9
SBS
170 echoerr " to be interpreted as pubkey strings (see \\'-r\\' option)."
171 echoerr " -b, --buffer-ttl [integer]"
172 echoerr " Specify custom buffer period in seconds (default: 300 seconds)"
173 echoerr " -B, --script-ttl [integer]"
174 echoerr " Specify custom script time-to-live in seconds (default: \"day\")"
f7f33d33 175 echoerr
17d49005 176 echoerr "EXAMPLE: (bash script lines)"
320ac29c
SBS
177 echoerr "/bin/bash bkgpslog -v -e -c \\"
178 echoerr "-z \"UTC\" -t \"/dev/shm\" \\"
17d49005
SBS
179 echoerr "-r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \\"
180 echoerr "-r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \\"
181 echoerr "-o ~/Sync/Location"
032f4b05
SBS
182} # Display information on how to use this script.
183showVersion() {
184 echoerr "$SCRIPT_VERSION"
185} # Display script version.
186vbm() {
0b3dde05
SBS
187 # Usage: vbm "DEBUG:verbose message here"
188 # Description: Prints verbose message ("vbm") to stderr if OPTION_VERBOSE is set to "true".
189 # Input:
190 # - OPTION_VERBOSE variable set by processArguments function. (ex: "true", "false")
191 # - "$@" positional arguments fed to this function.
192 # Output: stderr
193 # Script function dependencies: echoerr
194 # External function dependencies: echo
195 # Last modified: 2020-04-11T23:57Z
196 # Last modified by: Steven Baltakatei Sandoval
197 # License: GPLv3+
198 # Ref./Attrib:
199
200 if [ "$OPTION_VERBOSE" = "true" ]; then
201 FUNCTION_TIME=$(date --iso-8601=ns); # Save current time in nano seconds.
202 echoerr "[$FUNCTION_TIME] ""$*"; # Display argument text.
032f4b05 203 fi
0b3dde05
SBS
204
205 # End function
206 return 0; # Function finished.
032f4b05
SBS
207} # Verbose message display function.
208processArguments() {
209 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
d6896d3b
SBS
210 #echoerr "DEBUG:Starting processArguments while loop."
211 #echoerr "DEBUG:Provided arguments are:""$*"
032f4b05 212 case "$1" in
c1bbf9f7 213 -h | --help) showUsage; exit 1;; # Display usage.
032f4b05 214 --version) showVersion; exit 1;; # Show version
c1bbf9f7 215 -v | --verbose) OPTION_VERBOSE="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode.
6c30388f 216 -o | --output) if [ -d "$2" ]; then DIR_OUT="$2"; vbm "DEBUG:DIR_OUT:$DIR_OUT"; shift; fi ;; # Define output directory.
6a2cd888
SBS
217 -e | --encrypt) OPTION_ENCRYPT="true"; vbm "DEBUG:Encrypted output mode enabled.";; # Enable encryption
218 -r | --recipient) OPTION_RECIPIENTS="true"; argRecPubKeys+=("$2"); vbm "STATUS:pubkey added:""$2"; shift;; # Add recipients
219 -c | --compress) OPTION_COMPRESS="true"; vbm "DEBUG:Compressed output mode enabled.";; # Enable compression
220 -z | --time-zone) try setTimeZoneEV "$2"; shift;; # Set timestamp timezone
c9c573da 221 -t | --temp-dir) OPTION_TMPDIR="true" && argTempDirPriority="$2"; shift;; # Set time zone
6a2cd888 222 -R | --recipient-dir) OPTION_RECIPIENTS="true"; OPTION_RECDIR="true" && argRecDir="$2"; shift;; # Add recipient watch dir
2af0d7d9
SBS
223 -b | --buffer-ttl) OPTION_CUSTOM_BUFFERTTL="true" && argCustomBufferTTL="$2"; shift;; # Set custom buffer period (default: 300 seconds)
224 -B | --script-ttl) OPTION_CUSTOM_SCRIPTTTL_TE="true" && argCustomScriptTTL="$2"; shift;; # Set custom script TTL (default: "day")
b0da06ca 225 *) echoerr "ERROR: Unrecognized argument: $1"; echoerr "STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
032f4b05
SBS
226 esac
227 shift
228 done
229} # Argument Processing
6c30388f
SBS
230setTimeZoneEV(){
231 # Desc: Set time zone environment variable TZ
232 # Usage: setTimeZoneEV arg1
233 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
234 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
235 # Output: exports TZ
236 # exit code 0 on success
237 # exit code 1 on incorrect number of arguments
238 # exit code 2 if unable to validate arg1
239 # Depends: yell, printenv, bash 5
240 # Tested on: Debian 10
241 ARG1="$1"
242 local tzDir returnState
243 if ! [[ $# -eq 1 ]]; then
244 yell "ERROR:Invalid argument count.";
245 return 1;
246 fi
247
248 # Read TZDIR env var if available
249 if printenv TZDIR 1>/dev/null 2>&1; then
250 tzDir="$(printenv TZDIR)";
251 else
252 tzDir="/usr/share/zoneinfo";
253 fi
254
255 # Validate TZ string
256 if ! [[ -f "$tzDir"/"$ARG1" ]]; then
257 yell "ERROR:Invalid time zone argument.";
258 return 2;
259 else
260 # Export ARG1 as TZ environment variable
261 TZ="$ARG1" && export TZ && returnState="true";
262 fi
263
264 # Determine function return code
265 if [ "$returnState" = "true" ]; then
266 return 0;
267 fi
268} # Exports TZ environment variable
c2aaff78
SBS
269timeUntilNextDay(){
270 # Desc: Report seconds until next day.
3395ae22 271 # Version: 1.0.0
c2aaff78 272 # Output: stdout: integer seconds until next day
8fbca23d 273 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
c2aaff78
SBS
274 # Usage: timeUntilNextDay
275 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
3395ae22
SBS
276 # Depends: date 8, echo 8, yell, try
277
c2aaff78 278 local returnState TIME_CURRENT TIME_NEXT_DAY SECONDS_UNTIL_NEXT_DAY
3395ae22 279
8fbca23d 280 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
c2aaff78
SBS
281 TIME_NEXT_DAY="$(date -d "$TIME_CURRENT next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
282 SECONDS_UNTIL_NEXT_DAY="$(( $(date +%s -d "$TIME_NEXT_DAY") - $(date +%s -d "$TIME_CURRENT") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
283 if [[ "$SECONDS_UNTIL_NEXT_DAY" -gt 0 ]]; then
284 returnState="true";
285 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -eq 0 ]]; then
3395ae22 286 returnState="warning_zero";
c2aaff78
SBS
287 yell "WARNING:Reported time until next day exactly zero.";
288 elif [[ "$SECONDS_UNTIL_NEXT_DAY" -lt 0 ]]; then
3395ae22 289 returnState="warning_negative";
c2aaff78
SBS
290 yell "WARNING:Reported time until next day is negative.";
291 fi
292
293 try echo "$SECONDS_UNTIL_NEXT_DAY"; # Report
294
3395ae22 295 # Determine function return code
c2aaff78
SBS
296 if [[ "$returnState" = "true" ]]; then
297 return 0;
3395ae22 298 elif [[ "$returnState" = "warning_zero" ]]; then
c2aaff78 299 return 1;
3395ae22 300 elif [[ "$returnState" = "warning_negative" ]]; then
c2aaff78
SBS
301 return 2;
302 fi
303} # Report seconds until next day
304timeUntilNextHour(){
305 # Desc: Report seconds until next hour
3395ae22 306 # Version 1.0.0
c2aaff78
SBS
307 # Output: stdout: integer seconds until next hour
308 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
309 # Usage: timeUntilNextHour
310 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
3395ae22 311
c2aaff78
SBS
312 local returnState TIME_CURRENT TIME_NEXT_HOUR SECONDS_UNTIL_NEXT_HOUR
313 TIME_CURRENT="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
314 TIME_NEXT_HOUR="$(date -d "$TIME_CURRENT next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
315 SECONDS_UNTIL_NEXT_HOUR="$(( $(date +%s -d "$TIME_NEXT_HOUR") - $(date +%s -d "$TIME_CURRENT") ))"; # Calculate seconds until next hour (res. 1 second).
316 if [[ "$SECONDS_UNTIL_NEXT_HOUR" -gt 0 ]]; then
8fbca23d 317 returnState="true";
c2aaff78 318 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -eq 0 ]]; then
3395ae22 319 returnState="warning_zero";
c2aaff78
SBS
320 yell "WARNING:Reported time until next hour exactly zero.";
321 elif [[ "$SECONDS_UNTIL_NEXT_HOUR" -lt 0 ]]; then
3395ae22 322 returnState="warning_negative";
c2aaff78 323 yell "WARNING:Reported time until next hour is negative.";
8fbca23d 324 fi
032f4b05 325
c2aaff78 326 try echo "$SECONDS_UNTIL_NEXT_HOUR"; # Report
8fbca23d 327
3395ae22 328 # Determine function return code
8fbca23d
SBS
329 if [[ "$returnState" = "true" ]]; then
330 return 0;
3395ae22 331 elif [[ "$returnState" = "warning_zero" ]]; then
8fbca23d 332 return 1;
3395ae22 333 elif [[ "$returnState" = "warning_negative" ]]; then
8fbca23d
SBS
334 return 2;
335 fi
c2aaff78 336} # Report seconds until next hour
8fbca23d
SBS
337dateTimeShort(){
338 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
e47e8048
SBS
339 # Usage: dateTimeShort ([str date])
340 # Version 1.1.0
341 # Input: arg1: 'date'-parsable timestamp string (optional)
8fbca23d 342 # Output: stdout: timestamp (ISO-8601, no separators)
e47e8048 343 # Depends: yell
c2aaff78 344 local TIME_CURRENT TIME_CURRENT_SHORT
e47e8048
SBS
345
346 argTime="$1";
347 # Get Current Time
8fbca23d 348 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
e47e8048
SBS
349 # Decide to parse current or supplied date
350 ## Check if time argument empty
351 if [[ -z "$argTime" ]]; then
352 ## T: Time argument empty, use current time
353 TIME_INPUT="$TIME_CURRENT";
354 else
355 ## F: Time argument exists, validate time
356 if date --date="$argTime" 1>/dev/null 2>&1; then
357 ### T: Time argument is valid; use it
358 TIME_INPUT="$argTime";
359 else
360 ### F: Time argument not valid; exit
361 yell "ERROR:Invalid time argument supplied. Exiting."; exit 1;
362 fi
363 fi
364 # Construct and deliver separator-les date string
365 TIME_CURRENT_SHORT="$(date -d "$TIME_INPUT" +%Y%m%dT%H%M%S%z)";
8fbca23d 366 echo "$TIME_CURRENT_SHORT";
6c30388f
SBS
367} # Get YYYYmmddTHHMMSS±zzzz
368dateShort(){
369 # Desc: Date without separators (YYYYmmdd)
e47e8048
SBS
370 # Usage: dateShort ([str date])
371 # Version: 1.1.0
372 # Input: arg1: 'date'-parsable timestamp string (optional)
6c30388f 373 # Output: stdout: date (ISO-8601, no separators)
e47e8048 374 # Depends: yell
6c30388f 375 local TIME_CURRENT DATE_CURRENT_SHORT
e47e8048
SBS
376
377 argTime="$1";
378 # Get Current Time
6c30388f 379 TIME_CURRENT="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
e47e8048
SBS
380 # Decide to parse current or supplied date
381 ## Check if time argument empty
382 if [[ -z "$argTime" ]]; then
383 ## T: Time argument empty, use current time
384 TIME_INPUT="$TIME_CURRENT";
385 else
386 ## F: Time argument exists, validate time
387 if date --date="$argTime" 1>/dev/null 2>&1; then
388 ### T: Time argument is valid; use it
389 TIME_INPUT="$argTime";
390 else
391 ### F: Time argument not valid; exit
392 yell "ERROR:Invalid time argument supplied. Exiting."; exit 1;
393 fi
394 fi
395 # Construct and deliver separator-les date string
396 DATE_CURRENT_SHORT="$(date -d "$TIME_INPUT" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
6c30388f
SBS
397 echo "$DATE_CURRENT_SHORT";
398} # Get YYYYmmdd
399timeDuration(){
a7b4a273 400 # Desc: Given seconds, output ISO-8601 duration string
6c30388f
SBS
401 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
402 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
a7b4a273
SBS
403 # Usage: timeDuration [1:seconds] ([2:precision])
404 # Version: 1.0.3
6c30388f
SBS
405 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
406 # arg2: precision level (optional; default=2)
407 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
a7b4a273
SBS
408 # exit code 0: success
409 # exit code 1: error_input
410 # exit code 2: error_unknown
6c30388f 411 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
3395ae22 412 # Depends: date 8 (gnucoreutils), yell,
a7b4a273 413 local returnState argSeconds argPrecision remainder precision witherPrecision
3395ae22
SBS
414 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
415 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
416 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
417
a7b4a273
SBS
418 argSeconds="$1"; # read arg1 (seconds)
419 argPrecision="$2"; # read arg2 (precision)
6c30388f 420 precision=2; # set default precision
6c30388f
SBS
421
422 # Check that between one and two arguments is supplied
423 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
424 yell "ERROR:Invalid number of arguments:$# . Exiting.";
a7b4a273 425 returnState="error_input"; fi
6c30388f 426
a7b4a273 427 # Check that argSeconds provided
6c30388f 428 if [[ $# -ge 1 ]]; then
a7b4a273
SBS
429 ## Check that argSeconds is a positive integer
430 if [[ "$argSeconds" =~ ^[[:digit:]]+$ ]]; then
3395ae22 431 :
6c30388f 432 else
a7b4a273
SBS
433 yell "ERROR:argSeconds not a digit.";
434 returnState="error_input";
6c30388f
SBS
435 fi
436 else
437 yell "ERROR:No argument provided. Exiting.";
438 exit 1;
439 fi
440
a7b4a273 441 # Consider whether argPrecision was provided
6c30388f 442 if [[ $# -eq 2 ]]; then
a7b4a273
SBS
443 # Check that argPrecision is a positive integer
444 if [[ "$argPrecision" =~ ^[[:digit:]]+$ ]] && [[ "$argPrecision" -gt 0 ]]; then
445 precision="$argPrecision";
6c30388f 446 else
a7b4a273
SBS
447 yell "ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
448 returnState="error_input";
6c30388f
SBS
449 fi;
450 else
3395ae22 451 :
6c30388f
SBS
452 fi;
453
a7b4a273 454 remainder="$argSeconds" ; # seconds
6c30388f
SBS
455 ## Calculate full years Y, update remainder
456 fullYears=$(( remainder / (365*24*60*60) ));
457 remainder=$(( remainder - (fullYears*365*24*60*60) ));
458 ## Calculate full months M, update remainder
459 fullMonths=$(( remainder / (30*24*60*60) ));
460 remainder=$(( remainder - (fullMonths*30*24*60*60) ));
461 ## Calculate full days D, update remainder
462 fullDays=$(( remainder / (24*60*60) ));
463 remainder=$(( remainder - (fullDays*24*60*60) ));
464 ## Calculate full hours H, update remainder
465 fullHours=$(( remainder / (60*60) ));
466 remainder=$(( remainder - (fullHours*60*60) ));
467 ## Calculate full minutes M, update remainder
468 fullMinutes=$(( remainder / (60) ));
469 remainder=$(( remainder - (fullMinutes*60) ));
470 ## Calculate full seconds S, update remainder
471 fullSeconds=$(( remainder / (1) ));
472 remainder=$(( remainder - (remainder*1) ));
473 ## Check which fields filled
474 if [[ $fullYears -gt 0 ]]; then hasYears="true"; else hasYears="false"; fi
475 if [[ $fullMonths -gt 0 ]]; then hasMonths="true"; else hasMonths="false"; fi
476 if [[ $fullDays -gt 0 ]]; then hasDays="true"; else hasDays="false"; fi
477 if [[ $fullHours -gt 0 ]]; then hasHours="true"; else hasHours="false"; fi
478 if [[ $fullMinutes -gt 0 ]]; then hasMinutes="true"; else hasMinutes="false"; fi
479 if [[ $fullSeconds -gt 0 ]]; then hasSeconds="true"; else hasSeconds="false"; fi
480
481 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
482 witherPrecision="false"
483
484 ### Years
485 if $hasYears && [[ $precision -gt 0 ]]; then
486 displayYears="true";
487 witherPrecision="true";
488 else
489 displayYears="false";
490 fi;
491 if $witherPrecision; then ((precision--)); fi;
492
493 ### Months
494 if $hasMonths && [[ $precision -gt 0 ]]; then
495 displayMonths="true";
496 witherPrecision="true";
497 else
498 displayMonths="false";
499 fi;
500 if $witherPrecision && [[ $precision -gt 0 ]]; then
501 displayMonths="true";
502 fi;
503 if $witherPrecision; then ((precision--)); fi;
504
505 ### Days
506 if $hasDays && [[ $precision -gt 0 ]]; then
507 displayDays="true";
508 witherPrecision="true";
509 else
510 displayDays="false";
511 fi;
512 if $witherPrecision && [[ $precision -gt 0 ]]; then
513 displayDays="true";
514 fi;
515 if $witherPrecision; then ((precision--)); fi;
516
517 ### Hours
518 if $hasHours && [[ $precision -gt 0 ]]; then
519 displayHours="true";
520 witherPrecision="true";
521 else
522 displayHours="false";
523 fi;
524 if $witherPrecision && [[ $precision -gt 0 ]]; then
525 displayHours="true";
526 fi;
527 if $witherPrecision; then ((precision--)); fi;
528
529 ### Minutes
530 if $hasMinutes && [[ $precision -gt 0 ]]; then
531 displayMinutes="true";
532 witherPrecision="true";
533 else
534 displayMinutes="false";
535 fi;
536 if $witherPrecision && [[ $precision -gt 0 ]]; then
537 displayMinutes="true";
538 fi;
539 if $witherPrecision; then ((precision--)); fi;
540
541 ### Seconds
542
543 if $hasSeconds && [[ $precision -gt 0 ]]; then
544 displaySeconds="true";
545 witherPrecision="true";
546 else
547 displaySeconds="false";
548 fi;
549 if $witherPrecision && [[ $precision -gt 0 ]]; then
550 displaySeconds="true";
551 fi;
552 if $witherPrecision; then ((precision--)); fi;
553
6c30388f
SBS
554 ## Determine whether or not the "T" separator is needed to separate date and time elements
555 if ( $displayHours || $displayMinutes || $displaySeconds); then
556 displayDateTime="true"; else displayDateTime="false"; fi
557
558 ## Construct duration output string
559 OUTPUT="P"
560 if $displayYears; then
561 OUTPUT=$OUTPUT$fullYears"Y"; fi
562 if $displayMonths; then
563 OUTPUT=$OUTPUT$fullMonths"M"; fi
564 if $displayDays; then
565 OUTPUT=$OUTPUT$fullDays"D"; fi
566 if $displayDateTime; then
567 OUTPUT=$OUTPUT"T"; fi
568 if $displayHours; then
569 OUTPUT=$OUTPUT$fullHours"H"; fi
570 if $displayMinutes; then
571 OUTPUT=$OUTPUT$fullMinutes"M"; fi
572 if $displaySeconds; then
573 OUTPUT=$OUTPUT$fullSeconds"S"; fi
574
575 ## Output duration string to stdout
a7b4a273 576 echo "$OUTPUT" && returnState="true";
6c30388f
SBS
577
578 #===Determine function return code===
579 if [ "$returnState" = "true" ]; then
580 return 0;
a7b4a273
SBS
581 elif [ "$returnState" = "error_input" ]; then
582 yell "ERROR:input";
6c30388f 583 return 1;
a7b4a273
SBS
584 else
585 yell "ERROR:Unknown";
586 return 2;
6c30388f
SBS
587 fi
588
589} # Get duration (ex: PT10M4S )
590displayMissing() {
591 # Desc: Displays missing apps, files, and dirs
592 # Usage: displayMissing
593 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
594 # Output: stderr messages
595 #==BEGIN Display errors==
596 #===BEGIN Display Missing Apps===
597 missingApps="Missing apps :"
598 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
599 for key in "${!appRollCall[@]}"; do
600 value="${appRollCall[$key]}"
601 if [ "$value" = "false" ]; then
602 #echo "DEBUG:Missing apps: $key => $value";
603 missingApps="$missingApps""$key "
604 appMissing="true"
605 fi
606 done
607 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
608 echo "$missingApps" 1>&2;
609 fi
610 #===END Display Missing Apps===
611
612 #===BEGIN Display Missing Files===
613 missingFiles="Missing files:"
614 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
615 for key in "${!fileRollCall[@]}"; do
616 value="${fileRollCall[$key]}"
617 if [ "$value" = "false" ]; then
618 #echo "DEBUG:Missing files: $key => $value";
619 missingFiles="$missingFiles""$key "
620 fileMissing="true"
621 fi
622 done
623 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
624 echo "$missingFiles" 1>&2;
625 fi
626 #===END Display Missing Files===
627
628 #===BEGIN Display Missing Directories===
629 missingDirs="Missing dirs:"
630 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
631 for key in "${!dirRollCall[@]}"; do
632 value="${dirRollCall[$key]}"
633 if [ "$value" = "false" ]; then
634 #echo "DEBUG:Missing dirs: $key => $value";
635 missingDirs="$missingDirs""$key "
636 dirMissing="true"
637 fi
638 done
639 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
640 echo "$missingDirs" 1>&2;
641 fi
642 #===END Display Missing Directories===
643
644 #==END Display errors==
645} # Display missing apps, files, dirs
2af0d7d9
SBS
646magicSetScriptTTL() {
647 #Desc: Sets script_TTL seconds from provided time_element string argument
648 #Usage: magicSetScriptTTL [str time_element]
649 #Input: arg1: string (Ex: SCRIPT_TTL_TE; "day" or "hour")
650 #Output: var: SCRIPT_TTL (integer seconds)
651 #Depends: timeUntilNextHour, timeUntilNextDay
652 local argTimeElement
653 argTimeElement="$1"
654 if [[ "$argTimeElement" = "day" ]]; then
6c30388f 655 # Set script lifespan to end at start of next day
2af0d7d9
SBS
656 if ! SCRIPT_TTL="$(timeUntilNextDay)"; then
657 if [[ "$SCRIPT_TTL" -eq 0 ]]; then
658 ((SCRIPT_TTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
6c30388f
SBS
659 else
660 yell "ERROR: timeUntilNextDay exit code $?"; exit 1;
661 fi;
662 fi;
2af0d7d9 663 elif [[ "$argTimeElement" = "hour" ]]; then
6c30388f 664 # Set script lifespan to end at start of next hour
2af0d7d9
SBS
665 if ! SCRIPT_TTL="$(timeUntilNextHour)"; then
666 if [[ "$SCRIPT_TTL" -eq 0 ]]; then
667 ((SCRIPT_TTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
6c30388f
SBS
668 else
669 yell "ERROR: timeUntilNextHour exit code $?"; exit 1;
670 fi;
671 fi;
672 else
2af0d7d9 673 yell "ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
6c30388f
SBS
674 fi
675} # Seconds until next (day|hour).
f6fb18bd
SBS
676checkMakeTar() {
677 # Desc: Checks that a valid tar archive exists, creates one otherwise
678 # Usage: checkMakeTar [ path ]
2fc10824 679 # Version: 1.0.1
f6fb18bd 680 # Input: arg1: path of tar archive
2fc10824
SBS
681 # Output: exit code 0 : tar readable
682 # exit code 1 : tar missing; created
683 # exit code 2 : tar not readable; moved; replaced
f6fb18bd 684 # Depends: try, tar, date
2fc10824
SBS
685 local PATH_TAR returnFlag0 returnFlag1 returnFlag2
686 PATH_TAR="$1"
f6fb18bd
SBS
687
688 # Check if file is a valid tar archive
689 if tar --list --file="$PATH_TAR" 1>/dev/null 2>&1; then
690 ## T1: return success
2fc10824 691 returnFlag0="tar valid";
f6fb18bd
SBS
692 else
693 ## F1: Check if file exists
694 if [[ -f "$PATH_TAR" ]]; then
695 ### T: Rename file
2fc10824
SBS
696 try mv "$PATH_TAR" "$PATH_TAR""--broken--""$(date +%Y%m%dT%H%M%S)" && \
697 returnFlag1="tar moved";
f6fb18bd
SBS
698 else
699 ### F: -
700 :
701 fi
702 ## F2: Create tar archive, return 0
2fc10824
SBS
703 try tar --create --file="$PATH_TAR" --files-from=/dev/null && \
704 returnFlag2="tar created";
705 fi
706
707 # Determine function return code
708 if [[ "$returnFlag0" = "tar valid" ]]; then
f6fb18bd 709 return 0;
2fc10824
SBS
710 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
711 return 1; # tar missing so created
712 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
713 return 2; # tar not readable so moved; replaced
f6fb18bd
SBS
714 fi
715} # checks if arg1 is tar; creates one otherwise
66af6225
SBS
716appendArgTar(){
717 # Desc: Writes first argument to temporary file with arguments as options, then appends file to tar
e47e8048 718 # Usage: appendArgTar "$(echo "Data to be written.")" [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
f665ce95 719 # Version: 1.0.3
66af6225
SBS
720 # Input: arg1: data to be written
721 # arg2: file name of file to be inserted into tar
722 # arg3: tar archive path (must exist first)
723 # arg4: temporary working dir
724 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
725 # Output: file written to disk
726 # Example: decrypt multiple large files in parallel
727 # appendArgTar "$(cat /tmp/largefile1.gpg)" "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
728 # appendArgTar "$(cat /tmp/largefile2.gpg)" "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
729 # appendArgTar "$(cat /tmp/largefile3.gpg)" "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
66af6225 730 # Depends: bash 5
f665ce95 731 # Ref/Attrib: Using 'eval' to construct command strings https://askubuntu.com/a/476533
66af6225 732
d9fe1eba 733 # Save function name
d7138c7f
SBS
734 local FN="${FUNCNAME[0]}";
735 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
d9fe1eba 736
66af6225 737 # Set file name
d9fe1eba 738 if ! [ -z "$2" ]; then FILENAME="$2"; else yell "ERROR:$FN:Not enough arguments."; exit 1; fi
66af6225
SBS
739
740 # Check tar path is a file
d9fe1eba 741 if [ -f "$3" ]; then TAR_PATH="$3"; else yell "ERROR:$FN:Tar archive arg not a file."; exit 1; fi
66af6225
SBS
742
743 # Check temp dir arg
d9fe1eba 744 if ! [ -z "$4" ]; then TMP_DIR="$4"; else yell "ERROR:$FN:No temporary working dir set."; exit 1; fi
66af6225
SBS
745
746 # Set command strings
747 if ! [ -z "$5" ]; then CMD1="$5"; else CMD1="tee /dev/null "; fi # command string 1
748 if ! [ -z "$6" ]; then CMD2="$6"; else CMD2="tee /dev/null "; fi # command string 2
749 if ! [ -z "$7" ]; then CMD3="$7"; else CMD3="tee /dev/null "; fi # command string 3
750 if ! [ -z "$8" ]; then CMD4="$8"; else CMD4="tee /dev/null "; fi # command string 4
751
f665ce95
SBS
752 # Input command
753 CMD0="echo \"\$1\""
754
d7138c7f 755 # # Debug
f665ce95 756 # yell "DEBUG:STATUS:$FN:CMD0:$CMD0"
d7138c7f
SBS
757 # yell "DEBUG:STATUS:$FN:CMD1:$CMD1"
758 # yell "DEBUG:STATUS:$FN:CMD2:$CMD2"
759 # yell "DEBUG:STATUS:$FN:CMD3:$CMD3"
760 # yell "DEBUG:STATUS:$FN:CMD4:$CMD4"
761 # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME"
762 # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH"
763 # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR"
764
66af6225 765 # Write to temporary working dir
f665ce95 766 eval "$CMD0"" | ""$CMD1"" | ""$CMD2"" | ""$CMD3"" | ""$CMD4" > "$TMP_DIR"/"$FILENAME";
66af6225
SBS
767
768 # Append to tar
769 try tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
d7138c7f 770 #yell "DEBUG:STATUS:$FN:Finished appendArgTar()."
66af6225 771} # Append Bash var to file appended to Tar archive
e47e8048
SBS
772appendFileTar(){
773 # Desc: Processes first file and then appends to tar
774 # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([cmd1] [cmd2] [cmd3] [cmd4]...)
f665ce95 775 # Version: 1.0.2
e47e8048
SBS
776 # Input: arg1: path of file to be (processed and) written
777 # arg2: name to use for file inserted into tar
778 # arg3: tar archive path (must exist first)
779 # arg4: temporary working dir
780 # arg5+: command strings (ex: "gpsbabel -i nmea -f - -o kml -F - ")
781 # Output: file written to disk
782 # Example: decrypt multiple large files in parallel
783 # appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
784 # appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
785 # appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
786 # Depends: bash 5
787
788 # Save function name
789 local FN="${FUNCNAME[0]}";
790 #yell "DEBUG:STATUS:$FN:Finished appendFileTar()."
791
792 # Set file name
793 if ! [ -z "$2" ]; then FILENAME="$2"; else yell "ERROR:$FN:Not enough arguments."; exit 1; fi
794 # Check tar path is a file
795 if [ -f "$3" ]; then TAR_PATH="$3"; else yell "ERROR:$FN:Tar archive arg not a file."; exit 1; fi
796 # Check temp dir arg
797 if ! [ -z "$4" ]; then TMP_DIR="$4"; else yell "ERROR:$FN:No temporary working dir set."; exit 1; fi
798 # Set command strings
799 if ! [ -z "$5" ]; then CMD1="$5"; else CMD1="tee /dev/null "; fi # command string 1
800 if ! [ -z "$6" ]; then CMD2="$6"; else CMD2="tee /dev/null "; fi # command string 2
801 if ! [ -z "$7" ]; then CMD3="$7"; else CMD3="tee /dev/null "; fi # command string 3
802 if ! [ -z "$8" ]; then CMD4="$8"; else CMD4="tee /dev/null "; fi # command string 4
f665ce95
SBS
803
804 # Input command string
805 CMD0="cat \"\$1\""
806
e47e8048 807 # # Debug
f665ce95 808 # yell "DEBUG:STATUS:$FN:CMD0:$CMD0"
e47e8048
SBS
809 # yell "DEBUG:STATUS:$FN:CMD1:$CMD1"
810 # yell "DEBUG:STATUS:$FN:CMD2:$CMD2"
811 # yell "DEBUG:STATUS:$FN:CMD3:$CMD3"
812 # yell "DEBUG:STATUS:$FN:CMD4:$CMD4"
813 # yell "DEBUG:STATUS:$FN:FILENAME:$FILENAME"
814 # yell "DEBUG:STATUS:$FN:TAR_PATH:$TAR_PATH"
815 # yell "DEBUG:STATUS:$FN:TMP_DIR:$TMP_DIR"
816
817 # Write to temporary working dir
f665ce95 818 eval "$CMD0 | $CMD1 | $CMD2 | $CMD3 | $CMD4" > "$TMP_DIR"/"$FILENAME";
e47e8048
SBS
819
820 # Append to tar
821 try tar --append --directory="$TMP_DIR" --file="$TAR_PATH" "$FILENAME";
822 #yell "DEBUG:STATUS:$FN:Finished appendFileTar()."
823} # Append file to Tar archive
320ac29c
SBS
824checkAgePubkey() {
825 # Desc: Checks if string is an age-compatible pubkey
826 # Usage: checkAgePubkey [str pubkey]
827 # Version: 0.1.2
828 # Input: arg1: string
829 # Output: return code 0: string is age-compatible pubkey
830 # return code 1: string is NOT an age-compatible pubkey
831 # age stderr (ex: there is stderr if invalid string provided)
832 # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 )
833
834 argPubkey="$1";
835
836 if echo "test" | age -a -r "$argPubkey" 1>/dev/null; then
837 return 0;
838 else
839 return 1;
840 fi;
841} # Check age pubkey
d6ba4173
SBS
842validateInput() {
843 # Desc: Validates Input
844 # Usage: validateInput [str input] [str input type]
2af0d7d9 845 # Version: 0.3.0
d6ba4173
SBS
846 # Input: arg1: string to validate
847 # arg2: string specifying input type (ex:"ssh_pubkey")
848 # Output: return code 0: if input string matched specified string type
849 # Depends: bash 5, yell
850
851 # Save function name
852 local FN="${FUNCNAME[0]}";
853
854 # Process arguments
855 argInput="$1";
856 argType="$2";
857 if [[ $# -gt 2 ]]; then yell "ERROR:$0:$FN:Too many arguments."; exit 1; fi;
858
859 # Check for blank
860 if [[ -z "$argInput" ]]; then return 1; fi
861
862 # Define input types
863 ## ssh_pubkey
864 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
865 if [[ "$argType" = "ssh_pubkey" ]]; then
866 if [[ "$argInput" =~ ^[[:alnum:]-]*[\ ]*[[:alnum:]+/=]*$ ]]; then
2af0d7d9 867 return 0; fi; fi;
d6ba4173
SBS
868
869 ## age_pubkey
870 ### Check for age1[:bech32:]
871 if [[ "$argType" = "age_pubkey" ]]; then
872 if [[ "$argInput" =~ ^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*$ ]]; then
873 return 0; fi; fi
874
2af0d7d9
SBS
875 ## integer
876 if [[ "$argType" = "integer" ]]; then
877 if [[ "$argInput" =~ ^[[:digit:]]*$ ]]; then
878 return 0; fi; fi;
879
880 ## time element (year, month, week, day, hour, minute, second)
881 if [[ "$argType" = "time_element" ]]; then
882 if [[ "$argInput" = "year" ]] || \
883 [[ "$argInput" = "month" ]] || \
884 [[ "$argInput" = "week" ]] || \
885 [[ "$argInput" = "day" ]] || \
886 [[ "$argInput" = "hour" ]] || \
887 [[ "$argInput" = "minute" ]] || \
888 [[ "$argInput" = "second" ]]; then
889 return 0; fi; fi;
890
d6ba4173
SBS
891 # Return error if no condition matched.
892 return 1;
893} # Validates strings
3395ae22
SBS
894magicWriteVersion() {
895 # Desc: Appends time-stamped VERSION to PATHOUT_TAR
896 # Usage: magicWriteVersion
ff22a93f 897 # Version: 0.1.0
3395ae22 898 # Input: CONTENT_VERSION, FILEOUT_VERSION, PATHOUT_TAR, DIR_TMP
ff22a93f
SBS
899 # Input: SCRIPT_VERSION, SCRIPT_URL, AGE_VERSION, AGE_URL, SCRIPT_HOSTNAME
900 # Output: appends tar PATHOUT_TAR
3395ae22 901 # Depends: dateTimeShort, appendArgTar
ff22a93f 902 local CONTENT_VERSION pubKeyIndex
3395ae22 903
ff22a93f 904 # Set VERSION file name
3395ae22 905 FILEOUT_VERSION="$(dateTimeShort)..VERSION";
ff22a93f
SBS
906
907 # Gather VERSION data in CONTENT_VERSION
908 CONTENT_VERSION="SCRIPT_VERSION=$SCRIPT_VERSION";
aa2d7921
SBS
909 #CONTENT_VERSION="$CONTENT_VERSION""\\n";
910 CONTENT_VERSION="$CONTENT_VERSION""\\n""SCRIPT_NAME=$SCRIPT_NAME";
911 CONTENT_VERSION="$CONTENT_VERSION""\\n""SCRIPT_URL=$SCRIPT_URL";
912 CONTENT_VERSION="$CONTENT_VERSION""\\n""AGE_VERSION=$AGE_VERSION";
913 CONTENT_VERSION="$CONTENT_VERSION""\\n""AGE_URL=$AGE_URL";
914 CONTENT_VERSION="$CONTENT_VERSION""\\n""DATE=$(date --iso-8601=seconds)";
915 CONTENT_VERSION="$CONTENT_VERSION""\\n""HOSTNAME=$SCRIPT_HOSTNAME";
ff22a93f
SBS
916 ## Add list of recipient pubkeys
917 for pubkey in "${recPubKeysValid[@]}"; do
918 ((pubKeyIndex++))
aa2d7921 919 CONTENT_VERSION="$CONTENT_VERSION""\\n""PUBKEY_$pubKeyIndex=$pubkey";
ff22a93f
SBS
920 done
921 ## Process newline escapes
4c0c5850
SBS
922 CONTENT_VERSION="$(echo -e "$CONTENT_VERSION")"
923
ff22a93f 924 # Write CONTENT_VERSION as file FILEOUT_VERSION and write-append to PATHOUT_TAR
3395ae22 925 appendArgTar "$CONTENT_VERSION" "$FILEOUT_VERSION" "$PATHOUT_TAR" "$DIR_TMP";
ff22a93f 926
3395ae22 927} # bkgpslog: write version data to PATHOUT_TAR via appendArgTar()
811f50f2 928magicGatherWriteBuffer() {
66af6225 929 # Desc: bkgpslog-specific meta function for writing data to DIR_TMP then appending each file to PATHOUT_TAR
e47e8048
SBS
930 # Inputs: PATHOUT_TAR FILEOUT_{NMEA,GPX,KML} CMD_CONV_{NMEA,GPX,KML} CMD_{COMPRESS,ENCRYPT} DIR_TMP,
931 # Inputs: BUFFER_TTL bufferTTL_STR SCRIPT_HOSTNAME CMD_COMPRESS_SUFFIX CMD_ENCRYPT_SUFFIX
f6fb18bd 932 # Depends: yell, try, vbm, appendArgTar, tar
1b0e6227 933 local FN="${FUNCNAME[0]}";
872c737e 934 wait; # Wait to avoid collision with older magicWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
811f50f2
SBS
935 # Create buffer file with unique name
936 PATHOUT_BUFFER="$DIR_TMP/buffer$SECONDS";
937 # Fill buffer
938 timeout "$BUFFER_TTL"s gpspipe -r -o "$PATHOUT_BUFFER" ;
11f61f1a 939 timeBufferStart="$(dateTimeShort "$(date --date="$BUFFER_TTL seconds ago")")"; # Note start time
e47e8048
SBS
940 vbm "DEBUG:STATUS:$FN:Started magicWriteBuffer().";
941 # Determine file paths (time is start of buffer period)
942 FILEOUT_BASENAME="$timeBufferStart""--""$bufferTTL_STR""..""$SCRIPT_HOSTNAME""_location" && vbm "STATUS:Set FILEOUT_BASENAME to:$FILEOUT_BASENAME";
943 ## Files saved to DIR_TMP
944 FILEOUT_NMEA="$FILEOUT_BASENAME".nmea"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_NMEA to:$FILEOUT_NMEA";
945 FILEOUT_GPX="$FILEOUT_BASENAME".gpx"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_GPX to:$FILEOUT_GPX";
946 FILEOUT_KML="$FILEOUT_BASENAME".kml"$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX" && vbm "STATUS:Set FILEOUT_KML to:$FILEOUT_KML";
947 PATHOUT_NMEA="$DIR_TMP"/"$FILEOUT_NMEA" && vbm "STATUS:Set PATHOUT_NMEA to:$PATHOUT_NMEA";
948 PATHOUT_GPX="$DIR_TMP"/"$FILEOUT_GPX" && vbm "STATUS:Set PATHOUT_GPX to:$PATHOUT_GPX";
949 PATHOUT_KML="$DIR_TMP"/"$FILEOUT_KML" && vbm "STATUS:Set PATHOUT_KML to:$PATHOUT_KML";
950 ## Files saved to disk (DIR_OUT)
951 ### one file per day (Ex: "20200731..hostname_location.[.gpx.gz].tar")
952 PATHOUT_TAR="$DIR_OUT"/"$(dateShort "$(date --date="$BUFFER_TTL seconds ago")")".."$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar && \
953 vbm "STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
954 # DEBUG: check vars
955 vbm "STATUS:DIR_TMP :$DIR_TMP";
956 vbm "STATUS:PATHOUT_TAR :$PATHOUT_TAR";
957 vbm "STATUS:PATHOUT_NMEA:$PATHOUT_NMEA";
958 vbm "STATUS:PATHOUT_GPX:$PATHOUT_GPX";
959 vbm "STATUS:PATHOUT_KML:$PATHOUT_KML";
960
961
3395ae22
SBS
962 # Validate PATHOUT_TAR as tar.
963 checkMakeTar "$PATHOUT_TAR";
964 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
965 if [[ $? -eq 1 ]] || [[ $? -eq 2 ]]; then magicWriteVersion; fi
f6fb18bd
SBS
966
967 # Write bufferBash to PATHOUT_TAR
e47e8048
SBS
968 appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_NMEA" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_NMEA" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write NMEA data
969 appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_GPX" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_GPX" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write GPX file
970 appendFileTar "$PATHOUT_BUFFER" "$FILEOUT_KML" "$PATHOUT_TAR" "$DIR_TMP" "$CMD_CONV_KML" "$CMD_COMPRESS" "$CMD_ENCRYPT"; # Write KML file
971
e0452100 972 # Remove secured chunks from DIR_TMP
e47e8048
SBS
973 rm "$PATHOUT_BUFFER" "$PATHOUT_NMEA" "$PATHOUT_GPX" "$PATHOUT_KML";
974 vbm "DEBUG:STATUS:$FN:Finished magicWriteBuffer().";
975} # write buffer to disk
320ac29c
SBS
976magicParseRecipientDir() {
977 # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory")
2038018c 978 # Inputs: vars: OPTION_RECDIR, argRecDir, OPTION_ENCRYPT
50324e94
SBS
979 # arry: recPubKeysValid
980 # Outputs: arry: recPubKeysValid
320ac29c
SBS
981 # Depends: processArguments,
982 local recFileLine updateRecipients recipientDir
983 declare -a candRecPubKeysValid
50324e94
SBS
984
985 # Check that '-e' and '-R' set
2038018c 986 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then
320ac29c
SBS
987 ### Check that argRecDir is a directory.
988 if [[ -d "$argRecDir" ]]; then
fd5ef60e 989 recipientDir="$argRecDir" && vbm "STATUS:Recipient watch directory detected:\"$recipientDir\"";
320ac29c
SBS
990 #### Initialize variable indicating outcome of pubkey review
991 unset updateRecipients
992 #### Add existing recipients
6010b172 993 candRecPubKeysValid=("${recPubKeysValidStatic[@]}");
320ac29c
SBS
994 #### Parse files in recipientDir
995 for file in "$recipientDir"/*; do
996 ##### Read first line of each file
fd5ef60e 997 recFileLine="$(head -n1 "$file")" && vbm "STATUS:Checking if pubkey:\"$recFileLine\"";
320ac29c
SBS
998 ##### check if first line is a valid pubkey
999 if checkAgePubkey "$recFileLine" && \
1000 ( validateInput "$recFileLine" "ssh_pubkey" || validateInput "$recFileLine" "age_pubkey"); then
1001 ###### T: add candidate pubkey to candRecPubKeysValid
fd5ef60e 1002 candRecPubKeysValid+=("$recFileLine") && vbm "STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\"";
320ac29c
SBS
1003 else
1004 ###### F: throw warning;
1005 yell "ERROR:Invalid recipient file detected. Not modifying recipient list."
1006 updateRecipients="false";
1007 fi;
1008 done
1009 #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected
2af0d7d9 1010 if ! [[ "$updateRecipients" = "false" ]]; then
fd5ef60e 1011 recPubKeysValid=("${candRecPubKeysValid[@]}") && vbm "STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[@]}\"";
320ac29c 1012 fi;
cfc25c90 1013 else
320ac29c
SBS
1014 yell "ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
1015 fi;
1016 fi;
50324e94 1017 # Handle case if '-e' set but '-R' not set
2038018c 1018 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECDIR" = "true" ]]; then
2af0d7d9 1019 yell "ERROR: \\'-e\\' set but \\'-R\\' is not set."; fi;
50324e94 1020 # Handle case if '-R' set but '-e' not set
2038018c 1021 if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECDIR" = "true" ]]; then
2af0d7d9 1022 yell "ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi;
320ac29c
SBS
1023} # Update recPubKeysValid with argRecDir
1024magicParseRecipientArgs() {
1025 # Desc: Parses recipient arguments specified by '-r' option
1026 # Input: vars: OPTION_ENCRYPT from processArguments()
1027 # arry: argRecPubKeys from processArguments()
1028 # Output: vars: CMD_ENCRYPT, CMD_ENCRYPT_SUFFIX
6010b172 1029 # arry: recPubKeysValid, recPubKeysValidStatic
320ac29c
SBS
1030 # Depends: checkapp(), checkAgePubkey(), validateInput(), processArguments()
1031 local recipients
cfc25c90 1032
6a2cd888
SBS
1033 # Check if encryption option active.
1034 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then
17d49005 1035 if checkapp age; then # Check that age is available.
320ac29c 1036 for pubkey in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
238775e6 1037 vbm "DEBUG:Testing pubkey string:$pubkey";
320ac29c 1038 if checkAgePubkey "$pubkey" && \
d6ba4173 1039 ( validateInput "$pubkey" "ssh_pubkey" || validateInput "$pubkey" "age_pubkey"); then
ff22a93f 1040 #### Form age recipient string
be9ef23e 1041 recipients="$recipients""-r '$pubkey' ";
405ce7df 1042 vbm "STATUS:Added pubkey for forming age recipient string:""$pubkey";
77361307 1043 vbm "DEBUG:recipients:""$recipients";
ff22a93f
SBS
1044 #### Add validated pubkey to recPubKeysValid array
1045 recPubKeysValid+=("$pubkey") && vbm "DEBUG:recPubkeysValid:pubkey added:$pubkey";
408a342b
SBS
1046 else
1047 yell "ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
6a2cd888 1048 fi;
17d49005 1049 done
320ac29c 1050 vbm "DEBUG:Finished processing argRecPubKeys array";
6010b172
SBS
1051 vbm "STATUS:Array of validated pubkeys:${recPubKeysValid[@]}";
1052 recPubKeysValidStatic="${recPubKeysValid[@]}"; # Save static image of pubkeys validated by this function
ff22a93f
SBS
1053
1054 ## Form age command string
be9ef23e
SBS
1055 CMD_ENCRYPT="age ""$recipients " && vbm "CMD_ENCRYPT:$CMD_ENCRYPT";
1056 CMD_ENCRYPT_SUFFIX=".age" && vbm "CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX";
17d49005
SBS
1057 else
1058 yell "ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
6a2cd888 1059 fi;
408a342b 1060 else
be9ef23e
SBS
1061 CMD_ENCRYPT="tee /dev/null " && vbm "CMD_ENCRYPT:$CMD_ENCRYPT";
1062 CMD_ENCRYPT_SUFFIX="" && vbm "CMD_ENCRYPT_SUFFIX:$CMD_ENCRYPT_SUFFIX";
408a342b 1063 vbm "DEBUG:Encryption not enabled."
6a2cd888
SBS
1064 fi;
1065 # Catch case if '-e' is set but '-r' or '-R' is not
1066 if [[ "$OPTION_ENCRYPT" = "true" ]] && [[ ! "$OPTION_RECIPIENTS" = "true" ]]; then
2af0d7d9 1067 yell "ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
6a2cd888
SBS
1068 # Catch case if '-r' or '-R' set but '-e' is not
1069 if [[ ! "$OPTION_ENCRYPT" = "true" ]] && [[ "$OPTION_RECIPIENTS" = "true" ]]; then
2af0d7d9 1070 yell "ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
320ac29c
SBS
1071} # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
1072magicParseCompressionArg() {
1073 # Desc: Parses compression arguments specified by '-c' option
1074 # Input: vars: OPTION_COMPRESS
1075 # Output: CMD_COMPRESS, CMD_COMPRESS_SUFFIX
1076 # Depends: checkapp(), vbm(), gzip,
408a342b
SBS
1077 if [[ "$OPTION_COMPRESS" = "true" ]]; then # Check if compression option active
1078 if checkapp gzip; then # Check if gzip available
be9ef23e
SBS
1079 CMD_COMPRESS="gzip " && vbm "CMD_COMPRESS:$CMD_COMPRESS";
1080 CMD_COMPRESS_SUFFIX=".gz" && vbm "CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX";
408a342b
SBS
1081 else
1082 yell "ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
286d9825 1083 fi
408a342b 1084 else
be9ef23e
SBS
1085 CMD_COMPRESS="tee /dev/null " && vbm "CMD_COMPRESS:$CMD_COMPRESS";
1086 CMD_COMPRESS_SUFFIX="" && vbm "CMD_COMPRESS_SUFFIX:$CMD_COMPRESS_SUFFIX";
1b0e6227 1087 vbm "DEBUG:Compression not enabled.";
320ac29c
SBS
1088 fi
1089} # Form compression cmd string and filename suffix
1090magicInitWorkingDir() {
1091 # Desc: Determine temporary working directory from defaults or user input
c9c573da
SBS
1092 # Usage: magicInitWorkignDir
1093 # Input: vars: OPTION_TEMPDIR, argTempDirPriority, DIR_TMP_DEFAULT
320ac29c
SBS
1094 # Input: vars: SCRIPT_TIME_START
1095 # Output: vars: DIR_TMP
1096 # Depends: processArguments(), vbm(), yell()
1097 # Parse '-t' option (user-specified temporary working dir)
1098 ## Set DIR_TMP_PARENT to user-specified value if specified
1099 local DIR_TMP_PARENT
1100
1101 if [[ "$OPTION_TMPDIR" = "true" ]]; then
1102 if [[ -d "$argTempDirPriority" ]]; then
1103 DIR_TMP_PARENT="$argTempDirPriority";
1104 else
c9c573da 1105 yell "WARNING:Specified temporary working directory not valid:$argTempDirPriority";
320ac29c
SBS
1106 exit 1; # Exit since user requires a specific temp dir and it is not available.
1107 fi;
1108 else
1109 ## Set DIR_TMP_PARENT to default or fallback otherwise
1110 if [[ -d "$DIR_TMP_DEFAULT" ]]; then
1111 DIR_TMP_PARENT="$DIR_TMP_DEFAULT";
1112 elif [[ -d /tmp ]]; then
1113 yell "WARNING:$DIR_TMP_DEFAULT not available. Falling back to /tmp .";
1114 DIR_TMP_PARENT="/tmp";
1115 else
1116 yell "ERROR:No valid working directory available. Exiting.";
1117 exit 1;
1118 fi
1119 fi;
1120 ## Set DIR_TMP using DIR_TMP_PARENT and nonce (SCRIPT_TIME_START)
1121 DIR_TMP="$DIR_TMP_PARENT"/"$SCRIPT_TIME_START""..bkgpslog" && vbm "DEBUG:Set DIR_TMP to:$DIR_TMP"; # Note: removed at end of main().
1122} # Sets working dir
2af0d7d9
SBS
1123magicParseCustomTTL() {
1124 # Desc: Set user-specified TTLs for buffer and script
1125 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
1126 # Input: vars: OPTION_CUSTOM_BUFFERTTL, OPTION_CUSTOM_SCRIPTTTL
1127 # Input: vars: BUFFER_TTL (integer), SCRIPT_TTL_TE (string)
1128 # Output: BUFFER_TTL (integer), SCRIPT_TTL_TE (string)
1129 # Depends validateInput(), showUsage(), yell
1130
1131 # React to '-b, --buffer-ttl' option
1132 if [[ "$OPTION_CUSTOM_BUFFERTTL" = "true" ]]; then
1133 ## T: Check if argCustomBufferTTL is an integer
1134 if validateInput "$argCustomBufferTTL" "integer"; then
1135 ### T: argCustomBufferTTL is an integer
1136 BUFFER_TTL="$argCustomBufferTTL";
1137 else
1138 ### F: argcustomBufferTTL is not an integer
1139 yell "ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage; exit 1;
1140 fi;
1141 ## F: do not change BUFFER_TTL
1142 fi;
1143
1144 # React to '-B, --script-ttl' option
1145 if [[ "$OPTION_CUSTOM_SCRIPTTTL_TE" = "true" ]]; then
1146 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
1147 if validateInput "$argCustomScriptTTL" "time_element"; then
1148 ### T: argCustomScriptTTL is a time element
1149 SCRIPT_TTL_TE="$argCustomScriptTTL";
1150 else
1151 ### F: argcustomScriptTTL is not a time element
1152 yell "ERROR:Invalid time element argument for custom script time-to-live."; showUsage; exit 1;
1153 fi;
1154 ## F: do not change SCRIPT_TTL_TE
1155 fi;
1156} # Sets custom script or buffer TTL if specified
1157
032f4b05 1158
320ac29c
SBS
1159main() {
1160 # Process arguments
1161 processArguments "$@";
1162 ## Act upon arguments
1163 ### Determine working directory
c9c573da 1164 magicInitWorkingDir; # Sets DIR_TMP from argTempDirPriority
320ac29c
SBS
1165 ### Set output encryption and compression option strings
1166 #### React to "-r" ("encryption recipients") option
6010b172 1167 magicParseRecipientArgs; # Updates recPubKeysValid, CMD_ENCRYPT[_SUFFIX] from argRecPubKeys
320ac29c 1168 #### React to "-c" ("compression") option
50324e94 1169 magicParseCompressionArg; # Updates CMD_COMPRESS[_SUFFIX]
320ac29c 1170 #### React to "-R" ("recipient directory") option
50324e94 1171 magicParseRecipientDir; # Updates recPubKeysValid
2af0d7d9
SBS
1172 #### React to custom buffer and script TTL options ("-b", "-B")
1173 magicParseCustomTTL; # Sets custom SCRIPT_TTL_TE and/or BUFFER_TTL if specified
320ac29c
SBS
1174
1175 # Check that critical apps and dirs are available, display missing ones.
1176 if ! checkapp gpspipe tar && ! checkdir "$DIR_OUT" "DIR_TMP"; then
6c30388f
SBS
1177 yell "ERROR:Critical components missing.";
1178 displayMissing; yell "Exiting."; exit 1; fi
1179
2af0d7d9
SBS
1180 # Set script lifespan (SCRIPT_TTL from SCRIPT_TTL_TE)
1181 magicSetScriptTTL "$SCRIPT_TTL_TE";
1182 ## Note: SCRIPT_TTL_TE is time element string (ex: "day") while SCRIPT_TTL is integer seconds
6c30388f 1183
2af0d7d9
SBS
1184 # File name substring (ISO-8601 duration from BUFFER_TTL)
1185 bufferTTL_STR="$(timeDuration "$BUFFER_TTL")";
6c30388f
SBS
1186
1187 # Init temp working dir
3a7b92f8 1188 try mkdir "$DIR_TMP" && vbm "DEBUG:Working dir creatd at:$DIR_TMP";
6c30388f
SBS
1189
1190 # Initialize 'tar' archive
1191 ## Define output tar path (note: each day gets *one* tar file (Ex: "20200731..hostname_location.[.gpx.gz].tar"))
9efeccb5
SBS
1192 PATHOUT_TAR="$DIR_OUT"/"$(dateShort)".."$SCRIPT_HOSTNAME""_location""$CMD_COMPRESS_SUFFIX""$CMD_ENCRYPT_SUFFIX".tar && \
1193 vbm "STATUS:Set PATHOUT_TAR to:$PATHOUT_TAR";
f6fb18bd
SBS
1194 ## Check that PATHOUT_TAR is a tar. Rename old and create empty one otherwise.
1195 checkMakeTar "$PATHOUT_TAR" && vbm "DEBUG:Confirmed or Created to be a tar:$PATHOUT_TAR";
6c30388f 1196 ## Append VERSION file to PATHOUT_TAR
3395ae22 1197 magicWriteVersion;
e47e8048
SBS
1198
1199 # Define GPS conversion commands
1200 CMD_CONV_NMEA="tee /dev/null " && vbm "STATUS:Set CMD_CONV_NMEA to:$CMD_CONV_NMEA"; # tee as passthrough
1201 CMD_CONV_GPX="gpsbabel -i nmea -f - -o gpx -F - " && vbm "STATUS:Set CMD_CONV_GPX to:$CMD_CONV_GPX"; # convert NMEA to GPX
1202 CMD_CONV_KML="gpsbabel -i nmea -f - -o kml -F - " && vbm "STATUS:Set CMD_CONV_KML to:$CMD_CONV_KML"; # convert NMEA to KML
811f50f2 1203
e47e8048 1204 # MAIN LOOP:Record gps data until script lifespan ends
2af0d7d9 1205 while [[ "$SECONDS" -lt "$SCRIPT_TTL" ]]; do
1a1738c4 1206 magicParseRecipientDir
811f50f2 1207 magicGatherWriteBuffer &
238775e6 1208 sleep "$BUFFER_TTL";
811f50f2 1209 done
cfc25c90 1210
e47e8048
SBS
1211 # Cleanup
1212 ## Remove DIR_TMP
cfc25c90 1213 try rm -r "$DIR_TMP";
0aabe6a3 1214
1b0e6227 1215 vbm "STATUS:Main function finished.";
8fbca23d
SBS
1216} # Main function.
1217#===END Declare local script functions===
1218#==END Define script parameters==
032f4b05
SBS
1219
1220
8fbca23d
SBS
1221#==BEGIN Perform work and exit==
1222main "$@" # Run main function.
1223exit 0;
1224#==END Perform work and exit==