fix(bklog):Add missing validateInput()
[EVA-2020-02.git] / exec / bklog
... / ...
CommitLineData
1#!/bin/bash
2# Desc: Compresses, encrypts, and writes stdin every 5 seconds
3
4#==BEGIN Define script parameters==
5#===BEGIN Initialize variables===
6
7# Logging Behavior parameters
8bufferTTL="300"; # Time-to-live (seconds) for each buffer round
9scriptTTL_TE="day"; # Time element at the end of which script terminates
10dirTmpDefault="/dev/shm"; # Default parent of working directory
11
12# Script Metadata
13scriptName="bklog"; # Define basename of script file.
14scriptVersion="0.1.6"; # Define version of script.
15scriptURL="https://gitlab.com/baltakatei/ninfacyzga-01"; # Define wesite hosting this script.
16scriptTimeStart="$(date +%Y%m%dT%H%M%S.%N)"; # YYYYmmddTHHMMSS.NNNNNNNNN
17scriptHostname=$(hostname); # Save hostname of system running this script.
18PATH="$HOME/.local/bin:$PATH"; # Add "$(systemd-path user-binaries)" path in case user apps saved there
19ageVersion="1.0.0-beta2"; # Define version of age (encryption program)
20ageURL="https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2"; # Define website hosting age.
21
22# Arrays
23declare -a buffer # array for storing while read buffer
24declare -a argRecPubKeys # array for processArguments function
25declare -a recPubKeysValid # array for storing both '-r' and '-R' recipient pubkeys
26declare -a recPubKeysValidStatic # for storing '-r' recipient pubkeys
27declare -a argProcStrings argProcFileExts # for storing buffer processing strings (ex: "gpsbabel -i nmea -f - -o gpx -F - ")
28declare -Ag appRollCall # Associative array for storing app status
29declare -Ag fileRollCall # Associative array for storing file status
30declare -Ag dirRollCall # Associative array for storing dir status
31declare -a procStrings procFileExts # Arrays for storing processing commands and resulting output file extensions
32
33# Variables
34optionVerbose=""; optionEncrypt=""; dirOut=""; optionEncrypt=""; dir_tmp="";
35cmd_compress="";cmd_compress_suffix=""; cmd_encrypt=""; cmd_encrypt_suffix="";
36
37#===END Initialize variables===
38
39#===BEGIN Declare local script functions===
40yell() { echo "$0: $*" >&2; } #o Yell, Die, Try Three-Fingered Claw technique
41die() { yell "$*"; exit 111; } #o Ref/Attrib: https://stackoverflow.com/a/25515370
42try() { "$@" || die "cannot $*"; } #o
43processArguments() {
44 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
45 case "$1" in
46 -v | --verbose) optionVerbose="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode.
47 -h | --help) showUsage; exit 1;; # Display usage.
48 --version) showVersion; exit 1;; # Show version
49 -o | --output) if [ -d "$2" ]; then dirOut="$2"; vbm "DEBUG:dirOut:$dirOut"; shift; fi ;; # Define output directory.
50 -e | --encrypt) optionEncrypt="true"; vbm "DEBUG:Encrypted output mode enabled.";; # Enable encryption
51 -r | --recipient) optionRecipients="true"; argRecPubKeys+=("$2"); vbm "STATUS:pubkey added:""$2"; shift;; # Add recipients
52 -c | --compress) optionCompress="true"; vbm "DEBUG:Compressed output mode enabled.";; # Enable compression
53 -z | --time-zone) try setTimeZoneEV "$2"; shift;; # Set timestamp timezone
54 -t | --temp-dir) optionTmpDir="true" && argTempDirPriority="$2"; shift;; # Set time zone
55 -R | --recipient-dir) optionRecipients="true"; optionRecDir="true" && argRecDir="$2"; shift;; # Add recipient watch dir
56 -b | --buffer-ttl) optionCustomBufferTTL="true" && argCustomBufferTTL="$2"; shift;; # Set custom buffer period (default: 300 seconds)
57 -B | --script-ttl) optionCustomScriptTTL_TE="true" && argCustomScriptTTL_TE="$2"; shift;; # Set custom script TTL (default: "day")
58 -p | --process-string) optionProcString="true" && argProcStrings+=("$2") && argProcFileExts+=("$3") && vbm "STATUS:file extension \"$2\" for output of processing string added:\"$3\""; shift; shift;;
59 -l | --label) optionLabel="true" && argLabel="$2"; vbm "DEBUG:Custom label received:$argLabel"; shift;;
60 -w | --store-raw) optionStoreRaw="true" && argRawFileExt="$2"; vbm "DEBUG:Raw stdin file extension received:$argRawFileExt"; shift;;
61 -W | --no-store-raw) optionNoStoreRaw="true"; vbm "DEBUG:Option selected to not store raw stdin data."; shift;;
62 *) yell "ERROR: Unrecognized argument: $1"; yell "STATUS:All arguments:$*"; exit 1;; # Handle unrecognized options.
63 esac
64 shift
65 done
66} # Argument Processing
67vbm() {
68 # Description: Prints verbose message ("vbm") to stderr if optionVerbose is set to "true".
69 # Usage: vbm "DEBUG:verbose message here"
70 # Version 0.1.2
71 # Input: arg1: string
72 # vars: optionVerbose
73 # Output: stderr
74 # Depends: bash 5.0.3, echo 8.30, date 8.30
75
76 if [ "$optionVerbose" = "true" ]; then
77 functionTime=$(date --iso-8601=ns); # Save current time in nano seconds.
78 echo "[$functionTime] ""$*" 1>&2; # Display argument text.
79 fi
80
81 # End function
82 return 0; # Function finished.
83} # Displays message if optionVerbose true
84checkapp() {
85 # Desc: If arg is a command, save result in assoc array 'appRollCall'
86 # Usage: checkapp arg1 arg2 arg3 ...
87 # Version: 0.1.1
88 # Input: global assoc. array 'appRollCall'
89 # Output: adds/updates key(value) to global assoc array 'appRollCall'
90 # Depends: bash 5.0.3
91 local returnState
92
93 #===Process Args===
94 for arg in "$@"; do
95 if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
96 appRollCall[$arg]="true";
97 if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
98 else
99 appRollCall[$arg]="false"; returnState="false";
100 fi;
101 done;
102
103 #===Determine function return code===
104 if [ "$returnState" = "true" ]; then
105 return 0;
106 else
107 return 1;
108 fi;
109} # Check that app exists
110checkfile() {
111 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
112 # Usage: checkfile arg1 arg2 arg3 ...
113 # Version: 0.1.1
114 # Input: global assoc. array 'fileRollCall'
115 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
116 # Output: returns 0 if app found, 1 otherwise
117 # Depends: bash 5.0.3
118 local returnState
119
120 #===Process Args===
121 for arg in "$@"; do
122 if [ -f "$arg" ]; then
123 fileRollCall["$arg"]="true";
124 if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
125 else
126 fileRollCall["$arg"]="false"; returnState="false";
127 fi;
128 done;
129
130 #===Determine function return code===
131 if [ "$returnState" = "true" ]; then
132 return 0;
133 else
134 return 1;
135 fi;
136} # Check that file exists
137checkdir() {
138 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
139 # Usage: checkdir arg1 arg2 arg3 ...
140 # Version 0.1.1
141 # Input: global assoc. array 'dirRollCall'
142 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
143 # Output: returns 0 if app found, 1 otherwise
144 # Depends: Bash 5.0.3
145 local returnState
146
147 #===Process Args===
148 for arg in "$@"; do
149 if [ -d "$arg" ]; then
150 dirRollCall["$arg"]="true";
151 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
152 else
153 dirRollCall["$arg"]="false"; returnState="false";
154 fi
155 done
156
157 #===Determine function return code===
158 if [ "$returnState" = "true" ]; then
159 return 0;
160 else
161 return 1;
162 fi
163} # Check that dir exists
164displayMissing() {
165 # Desc: Displays missing apps, files, and dirs
166 # Usage: displayMissing
167 # Version 0.1.1
168 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
169 # Output: stderr: messages indicating missing apps, file, or dirs
170 # Depends: bash 5, checkAppFileDir()
171 local missingApps value appMissing missingFiles fileMissing
172 local missingDirs dirMissing
173
174 #==BEGIN Display errors==
175 #===BEGIN Display Missing Apps===
176 missingApps="Missing apps :";
177 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
178 for key in "${!appRollCall[@]}"; do
179 value="${appRollCall[$key]}";
180 if [ "$value" = "false" ]; then
181 #echo "DEBUG:Missing apps: $key => $value";
182 missingApps="$missingApps""$key ";
183 appMissing="true";
184 fi;
185 done;
186 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
187 echo "$missingApps" 1>&2;
188 fi;
189 unset value;
190 #===END Display Missing Apps===
191
192 #===BEGIN Display Missing Files===
193 missingFiles="Missing files:";
194 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
195 for key in "${!fileRollCall[@]}"; do
196 value="${fileRollCall[$key]}";
197 if [ "$value" = "false" ]; then
198 #echo "DEBUG:Missing files: $key => $value";
199 missingFiles="$missingFiles""$key ";
200 fileMissing="true";
201 fi;
202 done;
203 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
204 echo "$missingFiles" 1>&2;
205 fi;
206 unset value;
207 #===END Display Missing Files===
208
209 #===BEGIN Display Missing Directories===
210 missingDirs="Missing dirs:";
211 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
212 for key in "${!dirRollCall[@]}"; do
213 value="${dirRollCall[$key]}";
214 if [ "$value" = "false" ]; then
215 #echo "DEBUG:Missing dirs: $key => $value";
216 missingDirs="$missingDirs""$key ";
217 dirMissing="true";
218 fi;
219 done;
220 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
221 echo "$missingDirs" 1>&2;
222 fi;
223 unset value;
224 #===END Display Missing Directories===
225
226 #==END Display errors==
227} # Display missing apps, files, dirs
228
229appendFileTar(){
230 # Desc: Appends [processed] file to tar
231 # Usage: appendFileTar [file path] [name of file to be inserted] [tar path] [temp dir] ([process cmd])
232 # Version: 2.0.1
233 # Input: arg1: path of file to be (processed and) written
234 # arg2: name to use for file inserted into tar
235 # arg3: tar archive path (must exist first)
236 # arg4: temporary working dir
237 # arg5: (optional) command string to process file (ex: "gpsbabel -i nmea -f - -o kml -F - ")
238 # Output: file written to disk
239 # Example: decrypt multiple large files in parallel
240 # appendFileTar /tmp/largefile1.gpg "largefile1" $HOME/archive.tar /tmp "gpg --decrypt" &
241 # appendFileTar /tmp/largefile2.gpg "largefile2" $HOME/archive.tar /tmp "gpg --decrypt" &
242 # appendFileTar /tmp/largefile3.gpg "largefile3" $HOME/archive.tar /tmp "gpg --decrypt" &
243 # Depends: bash 5.0.3, tar 1.30, cat 8.30, yell()
244 local fn fileName tarPath tmpDir
245
246 # Save function name
247 fn="${FUNCNAME[0]}";
248 #yell "DEBUG:STATUS:$fn:Started appendFileTar()."
249
250 # Set file name
251 if ! [ -z "$2" ]; then fileName="$2"; else yell "ERROR:$fn:Not enough arguments."; exit 1; fi
252 # Check tar path is a file
253 if [ -f "$3" ]; then tarPath="$3"; else yell "ERROR:$fn:Tar archive arg not a file:$3"; exit 1; fi
254 # Check temp dir arg
255 if ! [ -z "$4" ]; then tmpDir="$4"; else yell "ERROR:$fn:No temporary working dir set."; exit 1; fi
256 # Set command strings
257 if ! [ -z "$5" ]; then cmd1="$5"; else cmd1="cat "; fi # command string
258
259 # Input command string
260 cmd0="cat \"\$1\"";
261
262 # Write to temporary working dir
263 eval "$cmd0 | $cmd1" > "$tmpDir"/"$fileName";
264
265 # Append to tar
266 try tar --append --directory="$tmpDir" --file="$tarPath" "$fileName";
267 #yell "DEBUG:STATUS:$fn:Finished appendFileTar()."
268} # Append [processed] file to Tar archive
269checkAgePubkey() {
270 # Desc: Checks if string is an age-compatible pubkey
271 # Usage: checkAgePubkey [str pubkey]
272 # Version: 0.1.2
273 # Input: arg1: string
274 # Output: return code 0: string is age-compatible pubkey
275 # return code 1: string is NOT an age-compatible pubkey
276 # age stderr (ex: there is stderr if invalid string provided)
277 # Depends: age (v0.1.0-beta2; https://github.com/FiloSottile/age/releases/tag/v1.0.0-beta2 )
278
279 argPubkey="$1";
280
281 if echo "test" | age -a -r "$argPubkey" 1>/dev/null; then
282 return 0;
283 else
284 return 1;
285 fi;
286} # Check age pubkey
287dateShort(){
288 # Desc: Date without separators (YYYYmmdd)
289 # Usage: dateShort ([str date])
290 # Version: 1.1.2
291 # Input: arg1: 'date'-parsable timestamp string (optional)
292 # Output: stdout: date (ISO-8601, no separators)
293 # Depends: bash 5.0.3, date 8.30, yell()
294 local argTime timeCurrent timeInput dateCurrentShort
295
296 argTime="$1";
297 # Get Current Time
298 timeCurrent="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
299 # Decide to parse current or supplied date
300 ## Check if time argument empty
301 if [[ -z "$argTime" ]]; then
302 ## T: Time argument empty, use current time
303 timeInput="$timeCurrent";
304 else
305 ## F: Time argument exists, validate time
306 if date --date="$argTime" 1>/dev/null 2>&1; then
307 ### T: Time argument is valid; use it
308 timeInput="$argTime";
309 else
310 ### F: Time argument not valid; exit
311 yell "ERROR:Invalid time argument supplied. Exiting."; exit 1;
312 fi;
313 fi;
314 # Construct and deliver separator-les date string
315 dateCurrentShort="$(date -d "$timeInput" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
316 echo "$dateCurrentShort";
317} # Get YYYYmmdd
318setTimeZoneEV(){
319 # Desc: Set time zone environment variable TZ
320 # Usage: setTimeZoneEV arg1
321 # Version 0.1.2
322 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
323 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
324 # Output: exports TZ
325 # exit code 0 on success
326 # exit code 1 on incorrect number of arguments
327 # exit code 2 if unable to validate arg1
328 # Depends: yell(), printenv 8.30, bash 5.0.3
329 # Tested on: Debian 10
330 local tzDir returnState argTimeZone
331
332 argTimeZone="$1"
333 if ! [[ $# -eq 1 ]]; then
334 yell "ERROR:Invalid argument count.";
335 return 1;
336 fi
337
338 # Read TZDIR env var if available
339 if printenv TZDIR 1>/dev/null 2>&1; then
340 tzDir="$(printenv TZDIR)";
341 else
342 tzDir="/usr/share/zoneinfo";
343 fi
344
345 # Validate TZ string
346 if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then
347 yell "ERROR:Invalid time zone argument.";
348 return 2;
349 else
350 # Export ARG1 as TZ environment variable
351 TZ="$argTimeZone" && export TZ && returnState="true";
352 fi
353
354 # Determine function return code
355 if [ "$returnState" = "true" ]; then
356 return 0;
357 fi
358} # Exports TZ environment variable
359showUsage() {
360 cat <<'EOF'
361 USAGE:
362 cmd | bklog [ options ]
363
364 OPTIONS:
365 -h, --help
366 Display help information.
367 --version
368 Display script version.
369 -v, --verbose
370 Display debugging info.
371 -e, --encrypt
372 Encrypt output.
373 -r, --recipient [ string pubkey ]
374 Specify recipient. May be age or ssh pubkey.
375 May be specified multiple times for multiple pubkeys.
376 See https://github.com/FiloSottile/age
377 -o, --output [ path dir ]
378 Specify output directory to save logs. This option is required
379 to save log data.
380 -p, --process-string [ filter command ] [ output file extension]
381 Specify how to create and name a processed version of the stdin.
382 For example, if stdin is 'nmea' location data:
383
384 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx"
385
386 This option would cause the stdin to 'bklog' to be piped into
387 the 'gpsbabel' command, interpreted as 'nmea' data, converted
388 into 'gpx' format, and then appended to the output tar file
389 as a file with a '.gpx' extension.
390 This option may be specified multiple times in order to output
391 results of multiple different processing methods.
392 -l, --label [ string ]
393 Specify a label to be included in all output file names.
394 Ex: 'location' if stdin is location data.
395 -w, --store-raw [ file extension ]
396 Specify file extension of file within output tar that contains
397 raw stdin data. The default behavior is to always save raw stdin
398 data in a '.stdin' file. Example usage when 'bklog' receives
399 'nmea' data from 'gpspipe -r':
400
401 -w ".nmea"
402
403 Stdin data is saved in a '.nmea' file within the output tar.
404 -W, --no-store-raw
405 Do not store raw stdin in output tar.
406 -c, --compress
407 Compress output with gzip (before encryption if enabled).
408 -z, --time-zone
409 Specify time zone. (ex: "America/New_York")
410 -t, --temp-dir [path dir]
411 Specify parent directory for temporary working directory.
412 Default: "/dev/shm"
413 -R, --recipient-dir [path dir]
414 Specify directory containing files whose first lines are
415 to be interpreted as pubkey strings (see '-r' option).
416 -b, --buffer-ttl [integer]
417 Specify custom buffer period in seconds (default: 300 seconds)
418 -B, --script-ttl [time element string]
419 Specify custom script time-to-live in seconds (default: "day")
420 Valid values: "day", "hour"
421
422 EXAMPLE: (bash script lines)
423 $ gpspipe -r | /bin/bash bklog -v -e -c -z "UTC" -t "/dev/shm" \
424 -r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \
425 -r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \
426 -R ~/.config/bklog/recipients -w ".nmea" -b 300 -B "day" \
427 -o ~/Sync/Logs -l "location" \
428 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" \
429 -p "gpsbabel -i nmea -f - -o kml -F - " ".kml"
430EOF
431} # Display information on how to use this script.
432showVersion() {
433 yell "$scriptVersion"
434} # Display script version.
435timeDuration(){
436 # Desc: Given seconds, output ISO-8601 duration string
437 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
438 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
439 # Usage: timeDuration [1:seconds] ([2:precision])
440 # Version: 1.0.4
441 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
442 # arg2: precision level (optional; default=2)
443 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
444 # exit code 0: success
445 # exit code 1: error_input
446 # exit code 2: error_unknown
447 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
448 # Depends: date 8, bash 5, yell,
449 local argSeconds argPrecision precision returnState remainder
450 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
451 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
452 local witherPrecision output
453 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
454
455 argSeconds="$1"; # read arg1 (seconds)
456 argPrecision="$2"; # read arg2 (precision)
457 precision=2; # set default precision
458
459 # Check that between one and two arguments is supplied
460 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
461 yell "ERROR:Invalid number of arguments:$# . Exiting.";
462 returnState="error_input"; fi
463
464 # Check that argSeconds provided
465 if [[ $# -ge 1 ]]; then
466 ## Check that argSeconds is a positive integer
467 if [[ "$argSeconds" =~ ^[[:digit:]]+$ ]]; then
468 :
469 else
470 yell "ERROR:argSeconds not a digit.";
471 returnState="error_input";
472 fi
473 else
474 yell "ERROR:No argument provided. Exiting.";
475 exit 1;
476 fi
477
478 # Consider whether argPrecision was provided
479 if [[ $# -eq 2 ]]; then
480 # Check that argPrecision is a positive integer
481 if [[ "$argPrecision" =~ ^[[:digit:]]+$ ]] && [[ "$argPrecision" -gt 0 ]]; then
482 precision="$argPrecision";
483 else
484 yell "ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
485 returnState="error_input";
486 fi;
487 else
488 :
489 fi;
490
491 remainder="$argSeconds" ; # seconds
492 ## Calculate full years Y, update remainder
493 fullYears=$(( remainder / (365*24*60*60) ));
494 remainder=$(( remainder - (fullYears*365*24*60*60) ));
495 ## Calculate full months M, update remainder
496 fullMonths=$(( remainder / (30*24*60*60) ));
497 remainder=$(( remainder - (fullMonths*30*24*60*60) ));
498 ## Calculate full days D, update remainder
499 fullDays=$(( remainder / (24*60*60) ));
500 remainder=$(( remainder - (fullDays*24*60*60) ));
501 ## Calculate full hours H, update remainder
502 fullHours=$(( remainder / (60*60) ));
503 remainder=$(( remainder - (fullHours*60*60) ));
504 ## Calculate full minutes M, update remainder
505 fullMinutes=$(( remainder / (60) ));
506 remainder=$(( remainder - (fullMinutes*60) ));
507 ## Calculate full seconds S, update remainder
508 fullSeconds=$(( remainder / (1) ));
509 remainder=$(( remainder - (remainder*1) ));
510 ## Check which fields filled
511 if [[ $fullYears -gt 0 ]]; then hasYears="true"; else hasYears="false"; fi
512 if [[ $fullMonths -gt 0 ]]; then hasMonths="true"; else hasMonths="false"; fi
513 if [[ $fullDays -gt 0 ]]; then hasDays="true"; else hasDays="false"; fi
514 if [[ $fullHours -gt 0 ]]; then hasHours="true"; else hasHours="false"; fi
515 if [[ $fullMinutes -gt 0 ]]; then hasMinutes="true"; else hasMinutes="false"; fi
516 if [[ $fullSeconds -gt 0 ]]; then hasSeconds="true"; else hasSeconds="false"; fi
517
518 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
519 witherPrecision="false"
520
521 ### Years
522 if $hasYears && [[ $precision -gt 0 ]]; then
523 displayYears="true";
524 witherPrecision="true";
525 else
526 displayYears="false";
527 fi;
528 if $witherPrecision; then ((precision--)); fi;
529
530 ### Months
531 if $hasMonths && [[ $precision -gt 0 ]]; then
532 displayMonths="true";
533 witherPrecision="true";
534 else
535 displayMonths="false";
536 fi;
537 if $witherPrecision && [[ $precision -gt 0 ]]; then
538 displayMonths="true";
539 fi;
540 if $witherPrecision; then ((precision--)); fi;
541
542 ### Days
543 if $hasDays && [[ $precision -gt 0 ]]; then
544 displayDays="true";
545 witherPrecision="true";
546 else
547 displayDays="false";
548 fi;
549 if $witherPrecision && [[ $precision -gt 0 ]]; then
550 displayDays="true";
551 fi;
552 if $witherPrecision; then ((precision--)); fi;
553
554 ### Hours
555 if $hasHours && [[ $precision -gt 0 ]]; then
556 displayHours="true";
557 witherPrecision="true";
558 else
559 displayHours="false";
560 fi;
561 if $witherPrecision && [[ $precision -gt 0 ]]; then
562 displayHours="true";
563 fi;
564 if $witherPrecision; then ((precision--)); fi;
565
566 ### Minutes
567 if $hasMinutes && [[ $precision -gt 0 ]]; then
568 displayMinutes="true";
569 witherPrecision="true";
570 else
571 displayMinutes="false";
572 fi;
573 if $witherPrecision && [[ $precision -gt 0 ]]; then
574 displayMinutes="true";
575 fi;
576 if $witherPrecision; then ((precision--)); fi;
577
578 ### Seconds
579
580 if $hasSeconds && [[ $precision -gt 0 ]]; then
581 displaySeconds="true";
582 witherPrecision="true";
583 else
584 displaySeconds="false";
585 fi;
586 if $witherPrecision && [[ $precision -gt 0 ]]; then
587 displaySeconds="true";
588 fi;
589 if $witherPrecision; then ((precision--)); fi;
590
591 ## Determine whether or not the "T" separator is needed to separate date and time elements
592 if ( $displayHours || $displayMinutes || $displaySeconds); then
593 displayDateTime="true"; else displayDateTime="false"; fi
594
595 ## Construct duration output string
596 output="P"
597 if $displayYears; then
598 output=$output$fullYears"Y"; fi
599 if $displayMonths; then
600 output=$output$fullMonths"M"; fi
601 if $displayDays; then
602 output=$output$fullDays"D"; fi
603 if $displayDateTime; then
604 output=$output"T"; fi
605 if $displayHours; then
606 output=$output$fullHours"H"; fi
607 if $displayMinutes; then
608 output=$output$fullMinutes"M"; fi
609 if $displaySeconds; then
610 output=$output$fullSeconds"S"; fi
611
612 ## Output duration string to stdout
613 echo "$output" && returnState="true";
614
615 #===Determine function return code===
616 if [ "$returnState" = "true" ]; then
617 return 0;
618 elif [ "$returnState" = "error_input" ]; then
619 yell "ERROR:input";
620 return 1;
621 else
622 yell "ERROR:Unknown";
623 return 2;
624 fi
625
626} # Get duration (ex: PT10M4S )
627validateInput() {
628 # Desc: Validates Input
629 # Usage: validateInput [str input] [str input type]
630 # Version: 0.3.1
631 # Input: arg1: string to validate
632 # arg2: string specifying input type (ex:"ssh_pubkey")
633 # Output: return code 0: if input string matched specified string type
634 # Depends: bash 5, yell()
635
636 local fn argInput argType
637
638 # Save function name
639 fn="${FUNCNAME[0]}";
640
641 # Process arguments
642 argInput="$1";
643 argType="$2";
644 if [[ $# -gt 2 ]]; then yell "ERROR:$0:$fn:Too many arguments."; exit 1; fi;
645
646 # Check for blank
647 if [[ -z "$argInput" ]]; then return 1; fi
648
649 # Define input types
650 ## ssh_pubkey
651 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
652 if [[ "$argType" = "ssh_pubkey" ]]; then
653 if [[ "$argInput" =~ ^[[:alnum:]-]*[\ ]*[[:alnum:]+/=]*$ ]]; then
654 return 0; fi; fi;
655
656 ## age_pubkey
657 ### Check for age1[:bech32:]
658 if [[ "$argType" = "age_pubkey" ]]; then
659 if [[ "$argInput" =~ ^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*$ ]]; then
660 return 0; fi; fi
661
662 ## integer
663 if [[ "$argType" = "integer" ]]; then
664 if [[ "$argInput" =~ ^[[:digit:]]*$ ]]; then
665 return 0; fi; fi;
666
667 ## time element (year, month, week, day, hour, minute, second)
668 if [[ "$argType" = "time_element" ]]; then
669 if [[ "$argInput" = "year" ]] || \
670 [[ "$argInput" = "month" ]] || \
671 [[ "$argInput" = "week" ]] || \
672 [[ "$argInput" = "day" ]] || \
673 [[ "$argInput" = "hour" ]] || \
674 [[ "$argInput" = "minute" ]] || \
675 [[ "$argInput" = "second" ]]; then
676 return 0; fi; fi;
677
678 # Return error if no condition matched.
679 return 1;
680} # Validates strings
681
682magicInitWorkingDir() {
683 # Desc: Determine temporary working directory from defaults or user input
684 # Usage: magicInitWorkingDir
685 # Input: vars: optionTmpDir, argTempDirPriority, dirTmpDefault
686 # Input: vars: scriptTimeStart
687 # Output: vars: dir_tmp
688 # Depends: bash 5.0.3, processArguments(), vbm(), yell()
689 # Parse '-t' option (user-specified temporary working dir)
690 ## Set dir_tmp_parent to user-specified value if specified
691 local dir_tmp_parent
692
693 if [[ "$optionTmpDir" = "true" ]]; then
694 if [[ -d "$argTempDirPriority" ]]; then
695 dir_tmp_parent="$argTempDirPriority";
696 else
697 yell "WARNING:Specified temporary working directory not valid:$argTempDirPriority";
698 exit 1; # Exit since user requires a specific temp dir and it is not available.
699 fi;
700 else
701 ## Set dir_tmp_parent to default or fallback otherwise
702 if [[ -d "$dirTmpDefault" ]]; then
703 dir_tmp_parent="$dirTmpDefault";
704 elif [[ -d /tmp ]]; then
705 yell "WARNING:$dirTmpDefault not available. Falling back to /tmp .";
706 dir_tmp_parent="/tmp";
707 else
708 yell "ERROR:No valid working directory available. Exiting.";
709 exit 1;
710 fi;
711 fi;
712 ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart)
713 dir_tmp="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm "DEBUG:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main().
714} # Sets working dir
715magicInitCheckTar() {
716 # Desc: Initializes or checks output tar
717 # input: vars: dirOut, bufferTTL, cmd_encrypt_suffix, cmd_compress_suffix
718 # input: vars: scriptHostname
719 # output: vars: pathout_tar
720 # depends: Bash 5.0.3, vbm(), dateShort(), checkMakeTar(), magicWriteVersion()
721
722 # Form pathout_tar
723 pathout_tar="$dirOut"/"$(dateShort "$(date --date="$bufferTTL seconds ago" --iso-8601=seconds)")".."$scriptHostname""$label""$cmd_compress_suffix""$cmd_encrypt_suffix".tar && \
724 vbm "STATUS:Set pathout_tar to:$pathout_tar";
725 # Validate pathout_tar as tar.
726 checkMakeTar "$pathout_tar";
727 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
728 vbm "exit status before magicWriteVersion:$?"
729 if [[ $? -eq 1 ]] || [[ $? -eq 2 ]]; then magicWriteVersion; fi
730} # Initialize tar, set pathout_tar
731magicParseCompressionArg() {
732 # Desc: Parses compression arguments specified by '-c' option
733 # Input: vars: optionCompress
734 # Output: cmd_compress, cmd_compress_suffix
735 # Depends: processArguments(), vbm(), checkapp(), gzip 1.9
736 if [[ "$optionCompress" = "true" ]]; then # Check if compression option active
737 if checkapp gzip; then # Check if gzip available
738 cmd_compress="gzip " && vbm "cmd_compress:$cmd_compress";
739 cmd_compress_suffix=".gz" && vbm "cmd_compress_suffix:$cmd_compress_suffix";
740 else
741 yell "ERROR:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
742 fi
743 else
744 cmd_compress="tee /dev/null " && vbm "cmd_compress:$cmd_compress";
745 cmd_compress_suffix="" && vbm "cmd_compress_suffix:$cmd_compress_suffix";
746 vbm "DEBUG:Compression not enabled.";
747 fi
748} # Form compression cmd string and filename suffix
749magicParseCustomTTL() {
750 # Desc: Set user-specified TTLs for buffer and script
751 # Usage: magicParseCustomTTL
752 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
753 # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE
754 # Input: vars: bufferTTL (integer), scriptTTL_TE (string)
755 # Output: bufferTTL (integer), scriptTTL_TE (string)
756 # Depends: Bash 5.0.3, yell(), vbm(), validateInput(), showUsage()
757
758 # React to '-b, --buffer-ttl' option
759 if [[ "$optionCustomBufferTTL" = "true" ]]; then
760 ## T: Check if argCustomBufferTTL is an integer
761 if validateInput "$argCustomBufferTTL" "integer"; then
762 ### T: argCustomBufferTTL is an integer
763 bufferTTL="$argCustomBufferTTL" && vbm "Custom bufferTTL from -b:$bufferTTL";
764 else
765 ### F: argcustomBufferTTL is not an integer
766 yell "ERROR:Invalid integer argument for custom buffer time-to-live."; showUsage; exit 1;
767 fi;
768 ## F: do not change bufferTTL
769 fi;
770
771 # React to '-B, --script-ttl' option
772 if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then
773 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
774 if validateInput "$argCustomScriptTTL_TE" "time_element"; then
775 ### T: argCustomScriptTTL is a time element
776 scriptTTL_TE="$argCustomScriptTTL_TE" && vbm "Custom scriptTTL_TE from -B:$scriptTTL_TE";
777 else
778 ### F: argcustomScriptTTL is not a time element
779 yell "ERROR:Invalid time element argument for custom script time-to-live."; showUsage; exit 1;
780 fi;
781 ## F: do not change scriptTTL_TE
782 fi;
783} # Sets custom script or buffer TTL if specified
784magicParseLabel() {
785 # Desc: Parses -l option to set label
786 # In : optionLabel, argLabel
787 # Out: vars: label
788 # Depends: Bash 5.0.3, vbm(), yell()
789
790 vbm "STATUS:Started magicParseLabel() function.";
791 # Do nothing if optionLabel not set to true.
792 if [[ ! "$optionLabel" = "true" ]]; then
793 vbm "STATUS:optionlabel not set to 'true'. Returning early.";
794 return;
795 fi;
796 # Set label if optionLabel is true
797 if [[ "$optionLabel" = "true" ]]; then
798 label="_""$argLabel";
799 vbm "STATUS:Set label:$label";
800 fi;
801 vbm "STATUS:Finished magicParseLabel() function.";
802} # Set label used in output file name
803magicParseProcessStrings() {
804 # Desc: Processes user-supplied process strings into process commands for appendFileTar().
805 # Usage: magicParseProcessStrings
806 # In : vars: optionProcString optionNoStoreRaw optionStoreRaw argRawFileExt
807 # arry: argProcStrings, argProcFileExts
808 # Out: arry: procStrings, procFileExts
809 # Depends Bash 5.0.3, yell(), vbm()
810 local rawFileExt
811
812 vbm "STATUS:Starting magicParseProcessStrings() function.";
813 # Validate input
814 ## Validate argRawFileExt
815 if [[ "$argRawFileExt" =~ ^[.][[:alnum:]]*$ ]]; then
816 rawFileExt="$argRawFileExt";
817 fi;
818
819 # Add default stdin output file entries for procStrings, procFileExts
820 ## Check if user specified that no raw stdin be saved.
821 if [[ ! "$optionNoStoreRaw" = "true" ]]; then
822 ### T: --no-store-raw not set. Store raw. Append procStrings with cat.
823 #### Append procStrings array
824 procStrings+=("cat ");
825 #### Check if --store-raw set.
826 if [[ "$optionStoreRaw" = "true" ]]; then
827 ##### T: --store-raw set. Append procFileExts with user-specified file ext
828 procFileExts+=("$rawFileExt");
829 else
830 ##### F: --store-raw not set. Append procFileExts with default ".stdin" file ext
831 ###### Append procFileExts array
832 procFileExts+=(".stdin");
833 fi;
834 else
835 ### F: --no-store-raw set. Do not store raw.
836 #### Do not append procStrings or procFileExts arrays.
837 :
838 fi;
839
840 # Do nothing more if optionProcString not set to true.
841 if [[ ! "$optionProcString" = "true" ]]; then
842 vbm "STATUS:optionProcString not set to 'true'. Returning early.";
843 return; fi;
844 # Validate input array indices
845 ## Make sure that argProcStrings and argProcFileExts have same index counts
846 if ! [[ "${#argProcStrings[@]}" -eq "${#argProcFileExts[@]}" ]]; then
847 yell "ERROR:Mismatch in number of elements in arrays argProcStrings and argProcFileExts:${#argProcStrings[@]} DNE ${#argProcFileExts[@]}";
848 yell "argProcStrings:${argProcStrings[*]}"; yell "argProcFileExts:${argProcFileExts[*]}"; exit 1; fi;
849 ## Make sure that no array elements are blank
850 for element in "${argProcStrings[@]}"; do
851 if [[ -z "$element" ]]; then yell "ERROR:Empty process string specified. Exiting."; exit 1; fi; done
852 for element in "${argProcFileExts[@]}"; do
853 if [[ -z "$element" ]]; then yell "ERROR:Empty output file extension specified. Exiting."; exit 1; fi; done
854 ## Make sure that no process string starts with '-' (ex: if only one arg supplied after '-p' option)
855 for element in "${argProcStrings[@]}"; do
856 if [[ ! "$element" =~ ^[-][[:print:]]*$ ]] && [[ "$element" =~ ^[[:print:]]*$ ]]; then
857 yell "ERROR:Illegal character '-' at start of process string element. Option syntax error?";
858 exit 1; fi; done;
859 vbm "STATUS:Quick check shows argProcStrings and argProcFileExts appear to have valid contents.";
860 procStrings=("${argProcStrings[@]}"); # Export process command strings
861 procFileExts=("${argProcFileExts[@]}"); # Export process command strings
862 vbm "STATUS:Finished magicParseProcessStrings() function.";
863} # Validate and save process strings and file extensions to arrays procStrings, procFileExts
864magicParseRecipientArgs() {
865 # Desc: Parses recipient arguments specified by '-r' option
866 # Input: vars: optionEncrypt, optionRecipients
867 # arry: argRecPubKeys from processArguments()
868 # Output: vars: cmd_encrypt, cmd_encrypt_suffix
869 # arry: recPubKeysValid, recPubKeysValidStatic
870 # Depends: processArguments(), yell(), vbm(), checkapp(), checkAgePubkey(), validateInput()
871 local recipients
872
873 # Check if encryption option active.
874 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
875 if checkapp age; then # Check that age is available.
876 for pubkey in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
877 vbm "DEBUG:Testing pubkey string:$pubkey";
878 if checkAgePubkey "$pubkey" && \
879 ( validateInput "$pubkey" "ssh_pubkey" || validateInput "$pubkey" "age_pubkey"); then
880 #### Form age recipient string
881 recipients="$recipients""-r '$pubkey' ";
882 vbm "STATUS:Added pubkey for forming age recipient string:""$pubkey";
883 vbm "DEBUG:recipients:""$recipients";
884 #### Add validated pubkey to recPubKeysValid array
885 recPubKeysValid+=("$pubkey") && vbm "DEBUG:recPubkeysValid:pubkey added:$pubkey";
886 else
887 yell "ERROR:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
888 fi;
889 done
890 vbm "DEBUG:Finished processing argRecPubKeys array";
891 vbm "STATUS:Array of validated pubkeys:${recPubKeysValid[*]}";
892 recPubKeysValidStatic=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function
893
894 ## Form age command string
895 cmd_encrypt="age ""$recipients " && vbm "cmd_encrypt:$cmd_encrypt";
896 cmd_encrypt_suffix=".age" && vbm "cmd_encrypt_suffix:$cmd_encrypt_suffix";
897 else
898 yell "ERROR:Encryption enabled but \"age\" not found. Exiting."; exit 1;
899 fi;
900 else
901 cmd_encrypt="tee /dev/null " && vbm "cmd_encrypt:$cmd_encrypt";
902 cmd_encrypt_suffix="" && vbm "cmd_encrypt_suffix:$cmd_encrypt_suffix";
903 vbm "DEBUG:Encryption not enabled."
904 fi;
905 # Catch case if '-e' is set but '-r' or '-R' is not
906 if [[ "$optionEncrypt" = "true" ]] && [[ ! "$optionRecipients" = "true" ]]; then
907 yell "ERROR:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
908 # Catch case if '-r' or '-R' set but '-e' is not
909 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
910 yell "ERROR:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
911} # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
912magicParseRecipientDir() {
913 # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory")
914 # Inputs: vars: optionEncrypt, optionRecDir, argRecDir,
915 # arry: recPubKeysValid
916 # Outputs: arry: recPubKeysValid
917 # Depends: processArguments(), yell(), vbm(), validateInput(), checkAgePubkey()
918 local recipientDir recFileLine updateRecipients
919 declare -a candRecPubKeysValid
920
921 # Check that '-e' and '-R' set
922 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
923 ### Check that argRecDir is a directory.
924 if [[ -d "$argRecDir" ]]; then
925 recipientDir="$argRecDir" && vbm "STATUS:Recipient watch directory detected:\"$recipientDir\"";
926 #### Initialize variable indicating outcome of pubkey review
927 unset updateRecipients
928 #### Add existing recipients
929 candRecPubKeysValid=("${recPubKeysValidStatic[@]}");
930 #### Parse files in recipientDir
931 for file in "$recipientDir"/*; do
932 ##### Read first line of each file
933 recFileLine="$(head -n1 "$file")" && vbm "STATUS:Checking if pubkey:\"$recFileLine\"";
934 ##### check if first line is a valid pubkey
935 if checkAgePubkey "$recFileLine" && \
936 ( validateInput "$recFileLine" "ssh_pubkey" || validateInput "$recFileLine" "age_pubkey"); then
937 ###### T: add candidate pubkey to candRecPubKeysValid
938 candRecPubKeysValid+=("$recFileLine") && vbm "STATUS:RecDir pubkey is valid pubkey:\"$recFileLine\"";
939 else
940 ###### F: throw warning;
941 yell "ERROR:Invalid recipient file detected. Not modifying recipient list."
942 updateRecipients="false";
943 fi;
944 done
945 #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected
946 if ! [[ "$updateRecipients" = "false" ]]; then
947 recPubKeysValid=("${candRecPubKeysValid[@]}") && vbm "STATUS:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\"";
948 fi;
949 else
950 yell "ERROR:$0:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
951 fi;
952 fi;
953 # Handle case if '-R' set but '-e' not set
954 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
955 yell "ERROR: \\'-R\\' is set but \\'-e\\' is not set."; fi;
956} # Update recPubKeysValid with argRecDir
957magicSetScriptTTL() {
958 #Desc: Sets script_TTL seconds from provided time_element string argument
959 #Usage: magicSetScriptTTL [str time_element]
960 #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour")
961 #Output: var: scriptTTL (integer seconds)
962 #Depends: timeUntilNextHour, timeUntilNextDay
963 local argTimeElement
964
965 argTimeElement="$1";
966 if [[ "$argTimeElement" = "day" ]]; then
967 # Set script lifespan to end at start of next day
968 if ! scriptTTL="$(timeUntilNextDay)"; then # sets scriptTTL, then checks exit code
969 if [[ "$scriptTTL" -eq 0 ]]; then
970 ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
971 else
972 yell "ERROR: timeUntilNextDay exit code $?"; exit 1;
973 fi;
974 fi;
975 elif [[ "$argTimeElement" = "hour" ]]; then
976 # Set script lifespan to end at start of next hour
977 if ! scriptTTL="$(timeUntilNextHour)"; then # sets scriptTTL, then checks exit code
978 if [[ "$scriptTTL" -eq 0 ]]; then
979 ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
980 else
981 yell "ERROR: timeUntilNextHour exit code $?"; exit 1;
982 fi;
983 fi;
984 else
985 yell "ERROR:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
986 fi;
987} # Set scriptTTL in seconds until next (day|hour).
988magicWriteVersion() {
989 # Desc: Appends time-stamped VERSION to pathout_tar
990 # Usage: magicWriteVersion
991 # Input: vars: pathout_tar, dir_tmp
992 # Input: vars: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname
993 # Input: array: recPubKeysValid
994 # Output: appends tar (pathout_tar)
995 # Depends: bash 5.0.3, dateTimeShort(), appendArgTar()
996 local fileoutVersion contentVersion pubKeyIndex pubKeyIndex
997
998 # Set VERSION file name
999 fileoutVersion="$(dateTimeShort)..VERSION";
1000
1001 # Gather VERSION data in contentVersion
1002 contentVersion="scriptVersion=$scriptVersion";
1003 #contentVersion="$contentVersion""\\n";
1004 contentVersion="$contentVersion""\\n""scriptName=$scriptName";
1005 contentVersion="$contentVersion""\\n""scriptURL=$scriptURL";
1006 contentVersion="$contentVersion""\\n""ageVersion=$ageVersion";
1007 contentVersion="$contentVersion""\\n""ageURL=$ageURL";
1008 contentVersion="$contentVersion""\\n""date=$(date --iso-8601=seconds)";
1009 contentVersion="$contentVersion""\\n""hostname=$scriptHostname";
1010 ## Add list of recipient pubkeys
1011 for pubkey in "${recPubKeysValid[@]}"; do
1012 ((pubKeyIndex++))
1013 contentVersion="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1014 done
1015 ## Process newline escapes
1016 contentVersion="$(echo -e "$contentVersion")"
1017
1018 # Write contentVersion as file fileoutVersion and write-append to pathout_tar
1019 appendArgTar "$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp";
1020} # write version data to pathout_tar via appendArgTar()
1021magicProcessWriteBuffer() {
1022 # Desc: process and write buffer
1023 # In : vars: bufferTTL bufferTTL_STR scriptHostname label dir_tmp SECONDS
1024 # : arry: buffer
1025 # Out: file:(pathout_tar)
1026 # Depends: Bash 5.0.3, date 8.30, yell(), vbm(), dateTimeShort(),
1027 ### Note: These arrays should all have the same number of elements:
1028 ### pathouts, fileouts, procFileExts, procStrings
1029
1030 local fn timeBufferStartLong timeBufferStart fileoutBasename
1031 local -a fileouts pathouts
1032 local writeCmd1 writeCmd2 writeCmd3 writeCmd4
1033
1034 vbm "DEBUG:STATUS:$fn:Started magicProcessWriteBuffer().";
1035 # Debug:Get function name
1036 fn="${FUNCNAME[0]}";
1037
1038 # Determine file paths (time is start of buffer period)
1039 ## Calculate start time
1040 timeBufferStartLong="$(date --date="$bufferTTL seconds ago" --iso-8601=seconds)" && \
1041 vbm "timeBufferStartLong:$timeBufferStartLong";
1042 timeBufferStart="$(dateTimeShort "$timeBufferStartLong" )" && \
1043 vbm "timeBufferStart:$timeBufferStart"; # Note start time YYYYmmddTHHMMSS+zzzz (no separators)
1044 ## Set common basename
1045 fileoutBasename="$timeBufferStart""--""$bufferTTL_STR""..""$scriptHostname""$label" && \
1046 vbm "STATUS:Set fileoutBasename to:$fileoutBasename";
1047 ## Determine output file name array
1048 ### in: fileOutBasename cmd_compress_suffix cmd_encrypt_suffix procFileExts
1049 for fileExt in "${procFileExts[@]}"; do
1050 fileouts+=("$fileoutBasename""$fileExt""$cmd_compress_suffix""$cmd_encrypt_suffix") && \
1051 vbm "STATUS:Added $fileExt to fileouts:${fileouts[*]}";
1052 done;
1053 for fileName in "${fileouts[@]}"; do
1054 pathouts+=("$dir_tmp"/"$fileName") && \
1055 vbm "STATUS:Added $fileName to pathouts:${pathouts[*]}";
1056 done;
1057 ## Update pathout_tar
1058 magicInitCheckTar;
1059
1060 # Process and write buffers to dir_tmp
1061 ## Prepare command strings
1062 writeCmd1="printf \"%s\\\\n\" \"\${buffer[@]}\""; # printf "%s\\n" "${buffer[@]}"
1063 #writeCmd2="" # NOTE: Specified by parsing array procStrings
1064 writeCmd3="$cmd_compress";
1065 writeCmd4="$cmd_encrypt";
1066
1067 ## Process buffer and write to dir_tmp
1068 for index in "${!pathouts[@]}"; do
1069 writeCmd2="${procStrings[$index]}"
1070 eval "$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" >> "${pathouts[$index]}";
1071 done;
1072
1073 # Append dir_tmp files to pathout_tar
1074 wait; # Wait to avoid collision with older magicProcessWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
1075 for index in "${!pathouts[@]}"; do
1076 appendFileTar "${pathouts[$index]}" "${fileouts[$index]}" "$pathout_tar" "$dir_tmp";
1077 done;
1078
1079 # Remove secured chunks from dir_tmp
1080 for path in "${pathouts[@]}"; do
1081 rm "$path";
1082 done;
1083
1084 vbm "DEBUG:STATUS:$fn:Finished magicProcessWriteBuffer().";
1085} # Process and Write buffer
1086
1087main() {
1088 # Process arguments
1089 processArguments "$@";
1090 ## Determine working directory
1091 magicInitWorkingDir; # Sets dir_tmp from argTempDirPriority
1092 ## Set output encryption and compression option strings
1093 ### React to "-e" and "-r" ("encryption recipients") options
1094 magicParseRecipientArgs; # Updates recPubKeysValid, cmd_encrypt[_suffix] from argRecPubKeys
1095 ### React to "-R" ("recipient directory") option
1096 magicParseRecipientDir; # Updates recPubKeysValid
1097 ### React to "-c" ("compression") option
1098 magicParseCompressionArg; # Updates cmd_compress[_suffix]
1099 ## React to "-b" and "-B" (custom buffer and script TTL) options
1100 magicParseCustomTTL; # Sets custom scriptTTL_TE and/or bufferTTL if specified
1101 ## React to "-p" (user-supplied process command and file extension strings) options
1102 magicParseProcessStrings; # Sets arrays: procStrings, procFileExts
1103 ## React to "-l" (output file label) option
1104 magicParseLabel; # sets label (ex: "_location")
1105 ## React to "-w" (how to name raw stdin file) option
1106 magicParseStoreRaw; # sets raw_suffix
1107
1108 # Perform secondary setup operations
1109 ## Set script lifespan (scriptTTL from scriptTTL_TE)
1110 magicSetScriptTTL "$scriptTTL_TE";
1111 ## File name substring (ISO-8601 duration from bufferTTL)
1112 bufferTTL_STR="$(timeDuration "$bufferTTL")" && vbm "DEBUG:bufferTTL_STR:$bufferTTL_STR";
1113 ## Init temp working dir
1114 try mkdir "$dir_tmp" && vbm "DEBUG:Working dir created at dir_tmp:$dir_tmp";
1115 ## Initialize output tar (set pathout_tar)
1116 magicInitCheckTar;
1117
1118 # Check vital apps, files, dirs
1119 if ! checkapp tar && ! checkdir "$dirOut" "dir_tmp"; then
1120 yell "ERROR:Critical components missing.";
1121 displayMissing; yell "Exiting."; exit 1; fi
1122
1123 # MAIN LOOP: Run until script TTL seconds pass
1124 bufferRound=0;
1125 while [[ $SECONDS -lt "scriptTTL" ]]; do
1126 bufferTOD="$((SECONDS + bufferTTL))"; # Set buffer round time-of-death
1127 lineCount=0; # Debug counter
1128 # Consume stdin to fill buffer until buffer time-of-death (TOD) arrives
1129 while read -r -t "$bufferTTL" line && [[ $SECONDS -lt "$bufferTOD" ]]; do
1130 # Append line to buffer array
1131 buffer+=("$line");
1132 echo "DEBUG:Processing line:$lineCount";
1133 echo "DEBUG:Current line :$line";
1134 echo "DEBUG:buf elem count :${#buffer[@]}";
1135 ((lineCount++));
1136 done;
1137 # Create dir_tmp if missing
1138 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
1139 # Update encryption recipient array
1140 magicParseRecipientDir; # Update recPubKeysValid with argRecDir
1141 # Export buffer to asynchronous processing.
1142 magicProcessWriteBuffer &
1143 unset buffer; # Clear buffer array for next bufferRound
1144 # Increment buffer round
1145 ((bufferRound++));
1146 done;
1147
1148 # Cleanup
1149 ## Remove dir_tmp
1150 try rm -r "$dir_tmp" && vbm "Removed dir_tmp:$dir_tmp";
1151
1152 vbm "STATUS:Main function finished.";
1153} # Main function
1154
1155#===END Declare local script functions===
1156#==END Define script parameters==
1157
1158#==BEGIN Perform work and exit==
1159main "$@" # Run main function.
1160exit 0;
1161#==END Perform work and exit==
1162
1163# Author: Steven Baltakatei Sandoval;
1164# License: GPLv3+