debug(bklog):Add debug msgs to write function
[EVA-2020-02.git] / exec / bklog
CommitLineData
6cbe7c0a
SBS
1#!/bin/bash
2# Desc: Compresses, encrypts, and writes stdin every 5 seconds
3
5938a598
SBS
4#==BEGIN Define script parameters==
5#===BEGIN Initialize variables===
adf766fc 6
5938a598 7# Logging Behavior parameters
c5da633d 8bufferTTL="300"; # Time-to-live (seconds) for each buffer round
adf766fc 9scriptTTL_TE="day"; # Time element at the end of which script terminates
c5da633d 10dirTmpDefault="/dev/shm"; # Default parent of working directory
5938a598
SBS
11
12# Script Metadata
5938a598 13scriptName="bklog"; # Define basename of script file.
21363b80 14scriptVersion="0.1.13"; # Define version of script.
c5da633d
SBS
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.
5938a598
SBS
21
22# Arrays
c5da633d
SBS
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
5938a598
SBS
32
33# Variables
c5da633d
SBS
34optionVerbose=""; optionEncrypt=""; dirOut=""; optionEncrypt=""; dir_tmp="";
35cmd_compress="";cmd_compress_suffix=""; cmd_encrypt=""; cmd_encrypt_suffix="";
5938a598
SBS
36
37#===END Initialize variables===
38
39#===BEGIN Declare local script functions===
6cbe7c0a
SBS
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
c5da633d
SBS
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
79bb6c16
SBS
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
34711384
SBS
287checkMakeTar() {
288 # Desc: Checks that a valid tar archive exists, creates one otherwise
289 # Usage: checkMakeTar [ path ]
290 # Version: 1.0.2
291 # Input: arg1: path of tar archive
292 # Output: exit code 0 : tar readable
293 # exit code 1 : tar missing; created
294 # exit code 2 : tar not readable; moved; replaced
295 # Depends: bash 5, date 8, tar 1, try()
296 local pathTar returnFlag0 returnFlag1 returnFlag2
297 pathTar="$1";
298
299 # Check if file is a valid tar archive
300 if tar --list --file="$pathTar" 1>/dev/null 2>&1; then
301 ## T1: return success
302 returnFlag0="tar valid";
303 else
304 ## F1: Check if file exists
305 if [[ -f "$pathTar" ]]; then
306 ### T: Rename file
307 try mv "$pathTar" "$pathTar""--broken--""$(date +%Y%m%dT%H%M%S)" && \
308 returnFlag1="tar moved";
309 else
310 ### F: -
311 :
312 fi;
313 ## F2: Create tar archive, return 0
314 try tar --create --file="$pathTar" --files-from=/dev/null && \
315 returnFlag2="tar created";
316 fi;
317
318 # Determine function return code
319 if [[ "$returnFlag0" = "tar valid" ]]; then
320 return 0;
321 elif [[ "$returnFlag2" = "tar created" ]] && ! [[ "$returnFlag1" = "tar moved" ]]; then
322 return 1; # tar missing so created
323 elif [[ "$returnFlag2" = "tar created" ]] && [[ "$returnFlag1" = "tar moved" ]]; then
324 return 2; # tar not readable so moved; replaced
325 fi;
326} # checks if arg1 is tar; creates one otherwise
79bb6c16
SBS
327dateShort(){
328 # Desc: Date without separators (YYYYmmdd)
329 # Usage: dateShort ([str date])
330 # Version: 1.1.2
331 # Input: arg1: 'date'-parsable timestamp string (optional)
332 # Output: stdout: date (ISO-8601, no separators)
333 # Depends: bash 5.0.3, date 8.30, yell()
334 local argTime timeCurrent timeInput dateCurrentShort
335
336 argTime="$1";
337 # Get Current Time
338 timeCurrent="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
339 # Decide to parse current or supplied date
340 ## Check if time argument empty
341 if [[ -z "$argTime" ]]; then
342 ## T: Time argument empty, use current time
343 timeInput="$timeCurrent";
344 else
345 ## F: Time argument exists, validate time
346 if date --date="$argTime" 1>/dev/null 2>&1; then
347 ### T: Time argument is valid; use it
348 timeInput="$argTime";
349 else
350 ### F: Time argument not valid; exit
351 yell "ERROR:Invalid time argument supplied. Exiting."; exit 1;
352 fi;
353 fi;
354 # Construct and deliver separator-les date string
355 dateCurrentShort="$(date -d "$timeInput" +%Y%m%d)"; # Produce separator-less current date with resolution 1 day.
356 echo "$dateCurrentShort";
357} # Get YYYYmmdd
8681fe1b
SBS
358dateTimeShort(){
359 # Desc: Timestamp without separators (YYYYmmddTHHMMSS+zzzz)
360 # Usage: dateTimeShort ([str date])
361 # Version 1.1.1
362 # Input: arg1: 'date'-parsable timestamp string (optional)
363 # Output: stdout: timestamp (ISO-8601, no separators)
364 # Depends: yell
365 local argTime timeCurrent timeInput timeCurrentShort
366
367 argTime="$1";
368 # Get Current Time
369 timeCurrent="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
370 # Decide to parse current or supplied date
371 ## Check if time argument empty
372 if [[ -z "$argTime" ]]; then
373 ## T: Time argument empty, use current time
374 timeInput="$timeCurrent";
375 else
376 ## F: Time argument exists, validate time
377 if date --date="$argTime" 1>/dev/null 2>&1; then
378 ### T: Time argument is valid; use it
379 timeInput="$argTime";
380 else
381 ### F: Time argument not valid; exit
382 yell "ERROR:Invalid time argument supplied. Exiting."; exit 1;
383 fi
384 fi
385 # Construct and deliver separator-les date string
386 timeCurrentShort="$(date -d "$timeInput" +%Y%m%dT%H%M%S%z)";
387 echo "$timeCurrentShort";
388} # Get YYYYmmddTHHMMSS±zzzz
79bb6c16
SBS
389setTimeZoneEV(){
390 # Desc: Set time zone environment variable TZ
391 # Usage: setTimeZoneEV arg1
392 # Version 0.1.2
393 # Input: arg1: 'date'-compatible timezone string (ex: "America/New_York")
394 # TZDIR env var (optional; default: "/usr/share/zoneinfo")
395 # Output: exports TZ
396 # exit code 0 on success
397 # exit code 1 on incorrect number of arguments
398 # exit code 2 if unable to validate arg1
399 # Depends: yell(), printenv 8.30, bash 5.0.3
400 # Tested on: Debian 10
401 local tzDir returnState argTimeZone
402
403 argTimeZone="$1"
404 if ! [[ $# -eq 1 ]]; then
405 yell "ERROR:Invalid argument count.";
406 return 1;
407 fi
408
409 # Read TZDIR env var if available
410 if printenv TZDIR 1>/dev/null 2>&1; then
411 tzDir="$(printenv TZDIR)";
412 else
413 tzDir="/usr/share/zoneinfo";
414 fi
415
416 # Validate TZ string
417 if ! [[ -f "$tzDir"/"$argTimeZone" ]]; then
418 yell "ERROR:Invalid time zone argument.";
419 return 2;
420 else
421 # Export ARG1 as TZ environment variable
422 TZ="$argTimeZone" && export TZ && returnState="true";
423 fi
424
425 # Determine function return code
426 if [ "$returnState" = "true" ]; then
427 return 0;
428 fi
429} # Exports TZ environment variable
c5da633d
SBS
430showUsage() {
431 cat <<'EOF'
432 USAGE:
433 cmd | bklog [ options ]
d59450a5 434
c5da633d
SBS
435 OPTIONS:
436 -h, --help
437 Display help information.
438 --version
439 Display script version.
440 -v, --verbose
441 Display debugging info.
442 -e, --encrypt
443 Encrypt output.
444 -r, --recipient [ string pubkey ]
445 Specify recipient. May be age or ssh pubkey.
446 May be specified multiple times for multiple pubkeys.
447 See https://github.com/FiloSottile/age
448 -o, --output [ path dir ]
449 Specify output directory to save logs. This option is required
450 to save log data.
451 -p, --process-string [ filter command ] [ output file extension]
452 Specify how to create and name a processed version of the stdin.
453 For example, if stdin is 'nmea' location data:
454
455 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx"
456
457 This option would cause the stdin to 'bklog' to be piped into
458 the 'gpsbabel' command, interpreted as 'nmea' data, converted
459 into 'gpx' format, and then appended to the output tar file
460 as a file with a '.gpx' extension.
461 This option may be specified multiple times in order to output
462 results of multiple different processing methods.
463 -l, --label [ string ]
464 Specify a label to be included in all output file names.
465 Ex: 'location' if stdin is location data.
466 -w, --store-raw [ file extension ]
467 Specify file extension of file within output tar that contains
468 raw stdin data. The default behavior is to always save raw stdin
469 data in a '.stdin' file. Example usage when 'bklog' receives
470 'nmea' data from 'gpspipe -r':
471
472 -w ".nmea"
473
474 Stdin data is saved in a '.nmea' file within the output tar.
475 -W, --no-store-raw
476 Do not store raw stdin in output tar.
477 -c, --compress
478 Compress output with gzip (before encryption if enabled).
479 -z, --time-zone
480 Specify time zone. (ex: "America/New_York")
481 -t, --temp-dir [path dir]
482 Specify parent directory for temporary working directory.
483 Default: "/dev/shm"
484 -R, --recipient-dir [path dir]
485 Specify directory containing files whose first lines are
486 to be interpreted as pubkey strings (see '-r' option).
487 -b, --buffer-ttl [integer]
488 Specify custom buffer period in seconds (default: 300 seconds)
489 -B, --script-ttl [time element string]
490 Specify custom script time-to-live in seconds (default: "day")
491 Valid values: "day", "hour"
d59450a5 492
c5da633d
SBS
493 EXAMPLE: (bash script lines)
494 $ gpspipe -r | /bin/bash bklog -v -e -c -z "UTC" -t "/dev/shm" \
495 -r age1mrmfnwhtlprn4jquex0ukmwcm7y2nxlphuzgsgv8ew2k9mewy3rs8u7su5 \
496 -r age1ala848kqrvxc88rzaauc6vc5v0fqrvef9dxyk79m0vjea3hagclswu0lgq \
497 -R ~/.config/bklog/recipients -w ".nmea" -b 300 -B "day" \
498 -o ~/Sync/Logs -l "location" \
499 -p "gpsbabel -i nmea -f - -o gpx -F - " ".gpx" \
be9b3684 500 -p "gpsbabel -i nmea -f - -o kml -F - " ".kml"
c5da633d
SBS
501EOF
502} # Display information on how to use this script.
503showVersion() {
504 yell "$scriptVersion"
505} # Display script version.
4e4707fb
SBS
506timeDuration(){
507 # Desc: Given seconds, output ISO-8601 duration string
508 # Ref/Attrib: ISO-8601:2004(E), §4.4.4.2 Representations of time intervals by duration and context information
509 # Note: "1 month" ("P1M") is assumed to be "30 days" (see ISO-8601:2004(E), §2.2.1.2)
510 # Usage: timeDuration [1:seconds] ([2:precision])
511 # Version: 1.0.4
512 # Input: arg1: seconds as base 10 integer >= 0 (ex: 3601)
513 # arg2: precision level (optional; default=2)
514 # Output: stdout: ISO-8601 duration string (ex: "P1H1S", "P2Y10M15DT10H30M20S")
515 # exit code 0: success
516 # exit code 1: error_input
517 # exit code 2: error_unknown
518 # Example: 'timeDuration 111111 3' yields 'P1DT6H51M'
519 # Depends: date 8, bash 5, yell,
520 local argSeconds argPrecision precision returnState remainder
521 local fullYears fullMonths fullDays fullHours fullMinutes fullSeconds
522 local hasYears hasMonths hasDays hasHours hasMinutes hasSeconds
523 local witherPrecision output
524 local displayYears displayMonths displayDays displayHours displayMinutes displaySeconds
525
526 argSeconds="$1"; # read arg1 (seconds)
527 argPrecision="$2"; # read arg2 (precision)
528 precision=2; # set default precision
529
530 # Check that between one and two arguments is supplied
531 if ! { [[ $# -ge 1 ]] && [[ $# -le 2 ]]; }; then
532 yell "ERROR:Invalid number of arguments:$# . Exiting.";
533 returnState="error_input"; fi
534
535 # Check that argSeconds provided
536 if [[ $# -ge 1 ]]; then
537 ## Check that argSeconds is a positive integer
538 if [[ "$argSeconds" =~ ^[[:digit:]]+$ ]]; then
539 :
540 else
541 yell "ERROR:argSeconds not a digit.";
542 returnState="error_input";
543 fi
544 else
545 yell "ERROR:No argument provided. Exiting.";
546 exit 1;
547 fi
548
549 # Consider whether argPrecision was provided
550 if [[ $# -eq 2 ]]; then
551 # Check that argPrecision is a positive integer
552 if [[ "$argPrecision" =~ ^[[:digit:]]+$ ]] && [[ "$argPrecision" -gt 0 ]]; then
553 precision="$argPrecision";
554 else
555 yell "ERROR:argPrecision not a positive integer. (is $argPrecision ). Leaving early.";
556 returnState="error_input";
557 fi;
558 else
559 :
560 fi;
561
562 remainder="$argSeconds" ; # seconds
563 ## Calculate full years Y, update remainder
564 fullYears=$(( remainder / (365*24*60*60) ));
565 remainder=$(( remainder - (fullYears*365*24*60*60) ));
566 ## Calculate full months M, update remainder
567 fullMonths=$(( remainder / (30*24*60*60) ));
568 remainder=$(( remainder - (fullMonths*30*24*60*60) ));
569 ## Calculate full days D, update remainder
570 fullDays=$(( remainder / (24*60*60) ));
571 remainder=$(( remainder - (fullDays*24*60*60) ));
572 ## Calculate full hours H, update remainder
573 fullHours=$(( remainder / (60*60) ));
574 remainder=$(( remainder - (fullHours*60*60) ));
575 ## Calculate full minutes M, update remainder
576 fullMinutes=$(( remainder / (60) ));
577 remainder=$(( remainder - (fullMinutes*60) ));
578 ## Calculate full seconds S, update remainder
579 fullSeconds=$(( remainder / (1) ));
580 remainder=$(( remainder - (remainder*1) ));
581 ## Check which fields filled
582 if [[ $fullYears -gt 0 ]]; then hasYears="true"; else hasYears="false"; fi
583 if [[ $fullMonths -gt 0 ]]; then hasMonths="true"; else hasMonths="false"; fi
584 if [[ $fullDays -gt 0 ]]; then hasDays="true"; else hasDays="false"; fi
585 if [[ $fullHours -gt 0 ]]; then hasHours="true"; else hasHours="false"; fi
586 if [[ $fullMinutes -gt 0 ]]; then hasMinutes="true"; else hasMinutes="false"; fi
587 if [[ $fullSeconds -gt 0 ]]; then hasSeconds="true"; else hasSeconds="false"; fi
588
589 ## Determine which fields to display (see ISO-8601:2004 §4.4.3.2)
590 witherPrecision="false"
591
592 ### Years
593 if $hasYears && [[ $precision -gt 0 ]]; then
594 displayYears="true";
595 witherPrecision="true";
596 else
597 displayYears="false";
598 fi;
599 if $witherPrecision; then ((precision--)); fi;
600
601 ### Months
602 if $hasMonths && [[ $precision -gt 0 ]]; then
603 displayMonths="true";
604 witherPrecision="true";
605 else
606 displayMonths="false";
607 fi;
608 if $witherPrecision && [[ $precision -gt 0 ]]; then
609 displayMonths="true";
610 fi;
611 if $witherPrecision; then ((precision--)); fi;
612
613 ### Days
614 if $hasDays && [[ $precision -gt 0 ]]; then
615 displayDays="true";
616 witherPrecision="true";
617 else
618 displayDays="false";
619 fi;
620 if $witherPrecision && [[ $precision -gt 0 ]]; then
621 displayDays="true";
622 fi;
623 if $witherPrecision; then ((precision--)); fi;
624
625 ### Hours
626 if $hasHours && [[ $precision -gt 0 ]]; then
627 displayHours="true";
628 witherPrecision="true";
629 else
630 displayHours="false";
631 fi;
632 if $witherPrecision && [[ $precision -gt 0 ]]; then
633 displayHours="true";
634 fi;
635 if $witherPrecision; then ((precision--)); fi;
636
637 ### Minutes
638 if $hasMinutes && [[ $precision -gt 0 ]]; then
639 displayMinutes="true";
640 witherPrecision="true";
641 else
642 displayMinutes="false";
643 fi;
644 if $witherPrecision && [[ $precision -gt 0 ]]; then
645 displayMinutes="true";
646 fi;
647 if $witherPrecision; then ((precision--)); fi;
648
649 ### Seconds
650
651 if $hasSeconds && [[ $precision -gt 0 ]]; then
652 displaySeconds="true";
653 witherPrecision="true";
654 else
655 displaySeconds="false";
656 fi;
657 if $witherPrecision && [[ $precision -gt 0 ]]; then
658 displaySeconds="true";
659 fi;
660 if $witherPrecision; then ((precision--)); fi;
661
662 ## Determine whether or not the "T" separator is needed to separate date and time elements
663 if ( $displayHours || $displayMinutes || $displaySeconds); then
664 displayDateTime="true"; else displayDateTime="false"; fi
665
666 ## Construct duration output string
667 output="P"
668 if $displayYears; then
669 output=$output$fullYears"Y"; fi
670 if $displayMonths; then
671 output=$output$fullMonths"M"; fi
672 if $displayDays; then
673 output=$output$fullDays"D"; fi
674 if $displayDateTime; then
675 output=$output"T"; fi
676 if $displayHours; then
677 output=$output$fullHours"H"; fi
678 if $displayMinutes; then
679 output=$output$fullMinutes"M"; fi
680 if $displaySeconds; then
681 output=$output$fullSeconds"S"; fi
682
683 ## Output duration string to stdout
684 echo "$output" && returnState="true";
685
686 #===Determine function return code===
687 if [ "$returnState" = "true" ]; then
688 return 0;
689 elif [ "$returnState" = "error_input" ]; then
690 yell "ERROR:input";
691 return 1;
692 else
693 yell "ERROR:Unknown";
694 return 2;
695 fi
696
697} # Get duration (ex: PT10M4S )
34711384
SBS
698timeUntilNextDay(){
699 # Desc: Report seconds until next day.
700 # Version: 1.0.2
701 # Output: stdout: integer seconds until next day
702 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
703 # Usage: timeUntilNextDay
704 # Usage: if ! myTTL="$(timeUntilNextDay)"; then yell "ERROR in if statement"; exit 1; fi
705 # Depends: date 8, echo 8, yell, try
706
707 local returnState timeCurrent timeNextDay secondsUntilNextDay returnState
708 timeCurrent="$(date --iso-8601=seconds)" ; # Produce `date`-parsable current timestamp with resolution of 1 second.
709 timeNextDay="$(date -d "$timeCurrent next day" --iso-8601=date)"; # Produce timestamp of beginning of tomorrow with resolution of 1 second.
710 secondsUntilNextDay="$(( $(date +%s -d "$timeNextDay") - $(date +%s -d "$timeCurrent") ))" ; # Calculate seconds until closest future midnight (res. 1 second).
711 if [[ "$secondsUntilNextDay" -gt 0 ]]; then
712 returnState="true";
713 elif [[ "$secondsUntilNextDay" -eq 0 ]]; then
714 returnState="warning_zero";
715 yell "WARNING:Reported time until next day exactly zero.";
716 elif [[ "$secondsUntilNextDay" -lt 0 ]]; then
717 returnState="warning_negative";
718 yell "WARNING:Reported time until next day is negative.";
719 fi
720
721 try echo "$secondsUntilNextDay"; # Report
722
723 # Determine function return code
724 if [[ "$returnState" = "true" ]]; then
725 return 0;
726 elif [[ "$returnState" = "warning_zero" ]]; then
727 return 1;
728 elif [[ "$returnState" = "warning_negative" ]]; then
729 return 2;
730 fi
731} # Report seconds until next day
732timeUntilNextHour(){
733 # Desc: Report seconds until next hour
734 # Version 1.0.1
735 # Output: stdout: integer seconds until next hour
736 # Output: exit code 0 if stdout > 0; 1 if stdout = 0; 2 if stdout < 0
737 # Usage: timeUntilNextHour
738 # Usage: if ! myTTL="$(timeUntilNextHour)"; then yell "ERROR in if statement"; exit 1; fi
739
740 local returnState timeCurrent timeNextHour secondsUntilNextHour
741 timeCurrent="$(date --iso-8601=seconds)"; # Produce `date`-parsable current timestamp with resolution of 1 second.
742 timeNextHour="$(date -d "$timeCurrent next hour" --iso-8601=hours)"; # Produce `date`-parsable current time stamp with resolution of 1 second.
743 secondsUntilNextHour="$(( $(date +%s -d "$timeNextHour") - $(date +%s -d "$timeCurrent") ))"; # Calculate seconds until next hour (res. 1 second).
744 if [[ "$secondsUntilNextHour" -gt 0 ]]; then
745 returnState="true";
746 elif [[ "$secondsUntilNextHour" -eq 0 ]]; then
747 returnState="warning_zero";
748 yell "WARNING:Reported time until next hour exactly zero.";
749 elif [[ "$secondsUntilNextHour" -lt 0 ]]; then
750 returnState="warning_negative";
751 yell "WARNING:Reported time until next hour is negative.";
752 fi;
753
754 try echo "$secondsUntilNextHour"; # Report
755
756 # Determine function return code
757 if [[ "$returnState" = "true" ]]; then
758 return 0;
759 elif [[ "$returnState" = "warning_zero" ]]; then
760 return 1;
761 elif [[ "$returnState" = "warning_negative" ]]; then
762 return 2;
763 fi;
764} # Report seconds until next hour
be6ce8f4
SBS
765validateInput() {
766 # Desc: Validates Input
767 # Usage: validateInput [str input] [str input type]
768 # Version: 0.3.1
769 # Input: arg1: string to validate
770 # arg2: string specifying input type (ex:"ssh_pubkey")
771 # Output: return code 0: if input string matched specified string type
772 # Depends: bash 5, yell()
773
774 local fn argInput argType
775
776 # Save function name
777 fn="${FUNCNAME[0]}";
778
779 # Process arguments
780 argInput="$1";
781 argType="$2";
782 if [[ $# -gt 2 ]]; then yell "ERROR:$0:$fn:Too many arguments."; exit 1; fi;
783
784 # Check for blank
785 if [[ -z "$argInput" ]]; then return 1; fi
786
787 # Define input types
788 ## ssh_pubkey
789 ### Check for alnum/dash base64 (ex: "ssh-rsa AAAAB3NzaC1yc2EAAA")
790 if [[ "$argType" = "ssh_pubkey" ]]; then
791 if [[ "$argInput" =~ ^[[:alnum:]-]*[\ ]*[[:alnum:]+/=]*$ ]]; then
792 return 0; fi; fi;
793
794 ## age_pubkey
795 ### Check for age1[:bech32:]
796 if [[ "$argType" = "age_pubkey" ]]; then
797 if [[ "$argInput" =~ ^age1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]*$ ]]; then
798 return 0; fi; fi
799
800 ## integer
801 if [[ "$argType" = "integer" ]]; then
802 if [[ "$argInput" =~ ^[[:digit:]]*$ ]]; then
803 return 0; fi; fi;
804
805 ## time element (year, month, week, day, hour, minute, second)
806 if [[ "$argType" = "time_element" ]]; then
807 if [[ "$argInput" = "year" ]] || \
808 [[ "$argInput" = "month" ]] || \
809 [[ "$argInput" = "week" ]] || \
810 [[ "$argInput" = "day" ]] || \
811 [[ "$argInput" = "hour" ]] || \
812 [[ "$argInput" = "minute" ]] || \
813 [[ "$argInput" = "second" ]]; then
814 return 0; fi; fi;
815
816 # Return error if no condition matched.
817 return 1;
818} # Validates strings
79bb6c16 819
4e4707fb
SBS
820magicInitWorkingDir() {
821 # Desc: Determine temporary working directory from defaults or user input
822 # Usage: magicInitWorkingDir
823 # Input: vars: optionTmpDir, argTempDirPriority, dirTmpDefault
824 # Input: vars: scriptTimeStart
825 # Output: vars: dir_tmp
826 # Depends: bash 5.0.3, processArguments(), vbm(), yell()
827 # Parse '-t' option (user-specified temporary working dir)
828 ## Set dir_tmp_parent to user-specified value if specified
80fbd111 829 local fn dir_tmp_parent
6fe054b4 830
80fbd111
SBS
831 # Save function name
832 fn="${FUNCNAME[0]}";
833
834 vbm "STATUS:$fn:Starting magicInitWorkingDir() function.";
4e4707fb
SBS
835 if [[ "$optionTmpDir" = "true" ]]; then
836 if [[ -d "$argTempDirPriority" ]]; then
837 dir_tmp_parent="$argTempDirPriority";
838 else
80fbd111 839 yell "WARNING:$fn:Specified temporary working directory not valid:$argTempDirPriority";
4e4707fb
SBS
840 exit 1; # Exit since user requires a specific temp dir and it is not available.
841 fi;
842 else
843 ## Set dir_tmp_parent to default or fallback otherwise
844 if [[ -d "$dirTmpDefault" ]]; then
845 dir_tmp_parent="$dirTmpDefault";
846 elif [[ -d /tmp ]]; then
80fbd111 847 yell "WARNING:$fn:$dirTmpDefault not available. Falling back to /tmp .";
4e4707fb
SBS
848 dir_tmp_parent="/tmp";
849 else
80fbd111 850 yell "ERROR:$fn:No valid working directory available. Exiting.";
4e4707fb
SBS
851 exit 1;
852 fi;
853 fi;
854 ## Set dir_tmp using dir_tmp_parent and nonce (scriptTimeStart)
80fbd111
SBS
855 dir_tmp="$dir_tmp_parent"/"$scriptTimeStart""..bkgpslog" && vbm "DEBUG:$fn:Set dir_tmp to:$dir_tmp"; # Note: removed at end of main().
856 vbm "STATUS:$fn:Finished magicInitWorkingDir() function.";
4e4707fb
SBS
857} # Sets working dir
858magicInitCheckTar() {
859 # Desc: Initializes or checks output tar
860 # input: vars: dirOut, bufferTTL, cmd_encrypt_suffix, cmd_compress_suffix
861 # input: vars: scriptHostname
862 # output: vars: pathout_tar
863 # depends: Bash 5.0.3, vbm(), dateShort(), checkMakeTar(), magicWriteVersion()
80fbd111 864 local fn
4e4707fb 865
80fbd111
SBS
866 # Save function name
867 fn="${FUNCNAME[0]}";
868
869 vbm "STATUS:$fn:Starting magicInitCheckTar() function.";
4e4707fb
SBS
870 # Form pathout_tar
871 pathout_tar="$dirOut"/"$(dateShort "$(date --date="$bufferTTL seconds ago" --iso-8601=seconds)")".."$scriptHostname""$label""$cmd_compress_suffix""$cmd_encrypt_suffix".tar && \
80fbd111 872 vbm "STATUS:$fn:Set pathout_tar to:$pathout_tar";
4e4707fb
SBS
873 # Validate pathout_tar as tar.
874 checkMakeTar "$pathout_tar";
875 ## Add VERSION file if checkMakeTar had to create a tar (exited 1) or replace one (exited 2)
80fbd111 876 vbm "STATUS:$fn:exit status before magicWriteVersion:$?"
6fe054b4 877 if [[ $? -eq 1 ]] || [[ $? -eq 2 ]]; then magicWriteVersion; fi
80fbd111 878 vbm "STATUS:$fn:Finished magicInitCheckTar() function.";
4e4707fb
SBS
879} # Initialize tar, set pathout_tar
880magicParseCompressionArg() {
881 # Desc: Parses compression arguments specified by '-c' option
882 # Input: vars: optionCompress
883 # Output: cmd_compress, cmd_compress_suffix
884 # Depends: processArguments(), vbm(), checkapp(), gzip 1.9
80fbd111 885 local fn
6fe054b4 886
80fbd111
SBS
887 # Save function name
888 fn="${FUNCNAME[0]}";
889
890 vbm "STATUS:$fn:Starting magicParseCompressionArg() function.";
4e4707fb
SBS
891 if [[ "$optionCompress" = "true" ]]; then # Check if compression option active
892 if checkapp gzip; then # Check if gzip available
80fbd111
SBS
893 cmd_compress="gzip " && vbm "STATUS:$fn:cmd_compress:$cmd_compress";
894 cmd_compress_suffix=".gz" && vbm "STATUS:$fn:cmd_compress_suffix:$cmd_compress_suffix";
4e4707fb 895 else
80fbd111 896 yell "ERROR:$fn:Compression enabled but \"gzip\" not found. Exiting."; exit 1;
6fe054b4 897 fi;
4e4707fb 898 else
80fbd111
SBS
899 cmd_compress="tee /dev/null " && vbm "STATUS:$fn:cmd_compress:$cmd_compress";
900 cmd_compress_suffix="" && vbm "STATUS:$fn:cmd_compress_suffix:$cmd_compress_suffix";
901 vbm "DEBUG:$fn:Compression not enabled.";
6fe054b4 902 fi;
80fbd111 903 vbm "STATUS:$fn:Starting magicParseCompressionArg() function.";
4e4707fb
SBS
904} # Form compression cmd string and filename suffix
905magicParseCustomTTL() {
906 # Desc: Set user-specified TTLs for buffer and script
907 # Usage: magicParseCustomTTL
908 # Input: vars: argCustomBufferTTL (integer), argCustomScriptTTL_TE (string)
909 # Input: vars: optionCustomBufferTTL, optionCustomScriptTTL_TE
910 # Input: vars: bufferTTL (integer), scriptTTL_TE (string)
911 # Output: bufferTTL (integer), scriptTTL_TE (string)
912 # Depends: Bash 5.0.3, yell(), vbm(), validateInput(), showUsage()
80fbd111
SBS
913 local fn
914
915 # Save function name
916 fn="${FUNCNAME[0]}";
4e4707fb 917
80fbd111 918 vbm "STATUS:$fn:Starting magicParseCustomTTL() function.";
4e4707fb
SBS
919 # React to '-b, --buffer-ttl' option
920 if [[ "$optionCustomBufferTTL" = "true" ]]; then
921 ## T: Check if argCustomBufferTTL is an integer
922 if validateInput "$argCustomBufferTTL" "integer"; then
923 ### T: argCustomBufferTTL is an integer
80fbd111 924 bufferTTL="$argCustomBufferTTL" && vbm "STATUS:$fn:Custom bufferTTL from -b:$bufferTTL";
4e4707fb
SBS
925 else
926 ### F: argcustomBufferTTL is not an integer
80fbd111 927 yell "ERROR:$fn:Invalid integer argument for custom buffer time-to-live."; showUsage; exit 1;
4e4707fb
SBS
928 fi;
929 ## F: do not change bufferTTL
930 fi;
931
932 # React to '-B, --script-ttl' option
933 if [[ "$optionCustomScriptTTL_TE" = "true" ]]; then
934 ## T: Check if argCustomScriptTTL is a time element (ex: "day", "hour")
935 if validateInput "$argCustomScriptTTL_TE" "time_element"; then
936 ### T: argCustomScriptTTL is a time element
80fbd111 937 scriptTTL_TE="$argCustomScriptTTL_TE" && vbm "STATUS:$fn:Custom scriptTTL_TE from -B:$scriptTTL_TE";
4e4707fb
SBS
938 else
939 ### F: argcustomScriptTTL is not a time element
80fbd111 940 yell "ERROR:$fn:Invalid time element argument for custom script time-to-live."; showUsage; exit 1;
4e4707fb
SBS
941 fi;
942 ## F: do not change scriptTTL_TE
6fe054b4 943 fi;
80fbd111 944 vbm "STATUS:$fn:Starting magicParseCustomTTL() function.";
4e4707fb
SBS
945} # Sets custom script or buffer TTL if specified
946magicParseLabel() {
947 # Desc: Parses -l option to set label
948 # In : optionLabel, argLabel
949 # Out: vars: label
950 # Depends: Bash 5.0.3, vbm(), yell()
80fbd111 951 local fn
4e4707fb 952
80fbd111
SBS
953 # Save function name
954 fn="${FUNCNAME[0]}";
955
956 vbm "STATUS:$fn:Started magicParseLabel() function.";
4e4707fb
SBS
957 # Do nothing if optionLabel not set to true.
958 if [[ ! "$optionLabel" = "true" ]]; then
80fbd111 959 vbm "STATUS:$fn:optionlabel not set to 'true'. Returning early.";
4e4707fb
SBS
960 return;
961 fi;
962 # Set label if optionLabel is true
963 if [[ "$optionLabel" = "true" ]]; then
964 label="_""$argLabel";
80fbd111 965 vbm "STATUS:$fn:Set label:$label";
4e4707fb 966 fi;
80fbd111 967 vbm "STATUS:$fn:Finished magicParseLabel() function.";
4e4707fb
SBS
968} # Set label used in output file name
969magicParseProcessStrings() {
970 # Desc: Processes user-supplied process strings into process commands for appendFileTar().
971 # Usage: magicParseProcessStrings
972 # In : vars: optionProcString optionNoStoreRaw optionStoreRaw argRawFileExt
973 # arry: argProcStrings, argProcFileExts
974 # Out: arry: procStrings, procFileExts
975 # Depends Bash 5.0.3, yell(), vbm()
80fbd111
SBS
976 local fn rawFileExt
977
978 # Save function name
979 fn="${FUNCNAME[0]}";
980
981 vbm "STATUS:$fn:Starting magicParseProcessStrings() function.";
982 vbm "STATUS:$fn:var:optionProcString:$optionProcString";
983 vbm "STATUS:$fn:var:optionNoStoreRaw:$optionNoStoreRaw";
984 vbm "STATUS:$fn:var:optionStoreRaw:$optionStoreRaw";
985 vbm "STATUS:$fn:var:argRawFileExt:$argRawFileExt";
986 vbm "STATUS:$fn:ary:argProcStrings:${argProcStrings[*]}";
987 vbm "STATUS:$fn:ary:argProcFileExts:${argProcFileExts[*]}"
4e4707fb
SBS
988 # Validate input
989 ## Validate argRawFileExt
990 if [[ "$argRawFileExt" =~ ^[.][[:alnum:]]*$ ]]; then
991 rawFileExt="$argRawFileExt";
992 fi;
993
994 # Add default stdin output file entries for procStrings, procFileExts
995 ## Check if user specified that no raw stdin be saved.
996 if [[ ! "$optionNoStoreRaw" = "true" ]]; then
997 ### T: --no-store-raw not set. Store raw. Append procStrings with cat.
998 #### Append procStrings array
999 procStrings+=("cat ");
1000 #### Check if --store-raw set.
1001 if [[ "$optionStoreRaw" = "true" ]]; then
1002 ##### T: --store-raw set. Append procFileExts with user-specified file ext
1003 procFileExts+=("$rawFileExt");
1004 else
1005 ##### F: --store-raw not set. Append procFileExts with default ".stdin" file ext
1006 ###### Append procFileExts array
1007 procFileExts+=(".stdin");
1008 fi;
1009 else
1010 ### F: --no-store-raw set. Do not store raw.
1011 #### Do not append procStrings or procFileExts arrays.
1012 :
1013 fi;
1014
1015 # Do nothing more if optionProcString not set to true.
1016 if [[ ! "$optionProcString" = "true" ]]; then
80fbd111 1017 vbm "STATUS:$fn:optionProcString not set to 'true'. Returning early.";
4e4707fb
SBS
1018 return; fi;
1019 # Validate input array indices
1020 ## Make sure that argProcStrings and argProcFileExts have same index counts
1021 if ! [[ "${#argProcStrings[@]}" -eq "${#argProcFileExts[@]}" ]]; then
80fbd111
SBS
1022 yell "ERROR:$fn:Mismatch in number of elements in arrays argProcStrings and argProcFileExts:${#argProcStrings[@]} DNE ${#argProcFileExts[@]}";
1023 yell "STATUS:$fn:argProcStrings:${argProcStrings[*]}"; yell "STATUS:$fn:argProcFileExts:${argProcFileExts[*]}"; exit 1; fi;
4e4707fb
SBS
1024 ## Make sure that no array elements are blank
1025 for element in "${argProcStrings[@]}"; do
80fbd111 1026 if [[ -z "$element" ]]; then yell "ERROR:$fn:Empty process string specified. Exiting."; exit 1; fi; done
4e4707fb 1027 for element in "${argProcFileExts[@]}"; do
80fbd111 1028 if [[ -z "$element" ]]; then yell "ERROR:$fn:Empty output file extension specified. Exiting."; exit 1; fi; done
4e4707fb
SBS
1029 ## Make sure that no process string starts with '-' (ex: if only one arg supplied after '-p' option)
1030 for element in "${argProcStrings[@]}"; do
61b243e0 1031 if [[ "$element" =~ ^[-][[:print:]]*$ ]] && [[ ! "$element" =~ ^[[:print:]]*$ ]]; then
80fbd111 1032 yell "ERROR:$fn:Illegal character '-' at start of process string element:\"$element\"";
4e4707fb 1033 exit 1; fi; done;
80fbd111 1034 vbm "STATUS:$fn:Quick check shows argProcStrings and argProcFileExts appear to have valid contents.";
4e4707fb
SBS
1035 procStrings=("${argProcStrings[@]}"); # Export process command strings
1036 procFileExts=("${argProcFileExts[@]}"); # Export process command strings
80fbd111 1037 vbm "STATUS:$fn:Finished magicParseProcessStrings() function.";
4e4707fb 1038} # Validate and save process strings and file extensions to arrays procStrings, procFileExts
c5da633d
SBS
1039magicParseRecipientArgs() {
1040 # Desc: Parses recipient arguments specified by '-r' option
1041 # Input: vars: optionEncrypt, optionRecipients
1042 # arry: argRecPubKeys from processArguments()
1043 # Output: vars: cmd_encrypt, cmd_encrypt_suffix
1044 # arry: recPubKeysValid, recPubKeysValidStatic
1045 # Depends: processArguments(), yell(), vbm(), checkapp(), checkAgePubkey(), validateInput()
80fbd111 1046 local fn recipients
c5da633d 1047
80fbd111
SBS
1048 # Save function name
1049 fn="${FUNCNAME[0]}";
1050
1051 vbm "STATUS:$fn:Starting magicParseRecipientArgs() function.";
c5da633d
SBS
1052 # Check if encryption option active.
1053 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
1054 if checkapp age; then # Check that age is available.
1055 for pubkey in "${argRecPubKeys[@]}"; do # Validate recipient pubkey strings by forming test message
80fbd111 1056 vbm "DEBUG:$fn:Testing pubkey string:$pubkey";
c5da633d
SBS
1057 if checkAgePubkey "$pubkey" && \
1058 ( validateInput "$pubkey" "ssh_pubkey" || validateInput "$pubkey" "age_pubkey"); then
1059 #### Form age recipient string
1060 recipients="$recipients""-r '$pubkey' ";
80fbd111
SBS
1061 vbm "STATUS:$fn:Added pubkey for forming age recipient string:""$pubkey";
1062 vbm "DEBUG:$fn:recipients:""$recipients";
c5da633d 1063 #### Add validated pubkey to recPubKeysValid array
80fbd111 1064 recPubKeysValid+=("$pubkey") && vbm "DEBUG:$fn:recPubkeysValid:pubkey added:$pubkey";
c5da633d 1065 else
80fbd111 1066 yell "ERROR:$fn:Exit code ""$?"". Invalid recipient pubkey string. Exiting."; exit 1;
c5da633d
SBS
1067 fi;
1068 done
80fbd111
SBS
1069 vbm "DEBUG:$fn:Finished processing argRecPubKeys array";
1070 vbm "STATUS:$fn:Array of validated pubkeys:${recPubKeysValid[*]}";
c5da633d
SBS
1071 recPubKeysValidStatic=("${recPubKeysValid[@]}"); # Save static image of pubkeys validated by this function
1072
1073 ## Form age command string
80fbd111
SBS
1074 cmd_encrypt="age ""$recipients " && vbm "STATUS:$fn:cmd_encrypt:$cmd_encrypt";
1075 cmd_encrypt_suffix=".age" && vbm "STATUS:$fn:cmd_encrypt_suffix:$cmd_encrypt_suffix";
c5da633d 1076 else
80fbd111 1077 yell "ERROR:$fn:Encryption enabled but \"age\" not found. Exiting."; exit 1;
c5da633d
SBS
1078 fi;
1079 else
80fbd111
SBS
1080 cmd_encrypt="tee /dev/null " && vbm "STATUS:$fn:cmd_encrypt:$cmd_encrypt";
1081 cmd_encrypt_suffix="" && vbm "STATUS:$fn:cmd_encrypt_suffix:$cmd_encrypt_suffix";
1082 vbm "DEBUG:$fn:Encryption not enabled."
c5da633d
SBS
1083 fi;
1084 # Catch case if '-e' is set but '-r' or '-R' is not
1085 if [[ "$optionEncrypt" = "true" ]] && [[ ! "$optionRecipients" = "true" ]]; then
80fbd111 1086 yell "ERROR:$fn:\\'-e\\' set but no \\'-r\\' or \\'-R\\' set."; exit 1; fi;
c5da633d
SBS
1087 # Catch case if '-r' or '-R' set but '-e' is not
1088 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecipients" = "true" ]]; then
80fbd111
SBS
1089 yell "ERROR:$fn:\\'-r\\' or \\'-R\\' set but \\'-e\\' is not set."; exit 1; fi;
1090 vbm "STATUS:$fn:Finished magicParseRecipientArgs() function.";
c5da633d
SBS
1091} # Populate recPubKeysValid with argRecPubKeys; form encryption cmd string and filename suffix
1092magicParseRecipientDir() {
1093 # Desc: Updates recPubKeysValid with pubkeys in dir specified by '-R' option ("recipient directory")
1094 # Inputs: vars: optionEncrypt, optionRecDir, argRecDir,
1095 # arry: recPubKeysValid
1096 # Outputs: arry: recPubKeysValid
1097 # Depends: processArguments(), yell(), vbm(), validateInput(), checkAgePubkey()
80fbd111 1098 local fn recipientDir recFileLine updateRecipients
c5da633d
SBS
1099 declare -a candRecPubKeysValid
1100
80fbd111
SBS
1101 # Save function name
1102 fn="${FUNCNAME[0]}";
1103
1104 vbm "STATUS:$fn:Starting magicParseRecipientDir() function.";
c5da633d
SBS
1105 # Check that '-e' and '-R' set
1106 if [[ "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
1107 ### Check that argRecDir is a directory.
1108 if [[ -d "$argRecDir" ]]; then
80fbd111 1109 recipientDir="$argRecDir" && vbm "STATUS:$fn:Recipient watch directory detected:\"$recipientDir\"";
c5da633d
SBS
1110 #### Initialize variable indicating outcome of pubkey review
1111 unset updateRecipients
1112 #### Add existing recipients
1113 candRecPubKeysValid=("${recPubKeysValidStatic[@]}");
1114 #### Parse files in recipientDir
1115 for file in "$recipientDir"/*; do
1116 ##### Read first line of each file
80fbd111 1117 recFileLine="$(head -n1 "$file")" && vbm "STATUS:$fn:Checking if pubkey:\"$recFileLine\"";
c5da633d
SBS
1118 ##### check if first line is a valid pubkey
1119 if checkAgePubkey "$recFileLine" && \
1120 ( validateInput "$recFileLine" "ssh_pubkey" || validateInput "$recFileLine" "age_pubkey"); then
1121 ###### T: add candidate pubkey to candRecPubKeysValid
80fbd111 1122 candRecPubKeysValid+=("$recFileLine") && vbm "STATUS:$fn:RecDir pubkey is valid pubkey:\"$recFileLine\"";
c5da633d
SBS
1123 else
1124 ###### F: throw warning;
80fbd111 1125 yell "ERROR:$fn:Invalid recipient file detected. Not modifying recipient list."
c5da633d
SBS
1126 updateRecipients="false";
1127 fi;
1128 done
1129 #### Write updated recPubKeysValid array to recPubKeysValid if no failure detected
1130 if ! [[ "$updateRecipients" = "false" ]]; then
80fbd111 1131 recPubKeysValid=("${candRecPubKeysValid[@]}") && vbm "STATUS:$fn:Wrote candRecPubkeysValid to recPubKeysValid:\"${recPubKeysValid[*]}\"";
c5da633d
SBS
1132 fi;
1133 else
80fbd111 1134 yell "ERROR:$fn:Recipient directory $argRecDir does not exist. Exiting."; exit 1;
c5da633d
SBS
1135 fi;
1136 fi;
1137 # Handle case if '-R' set but '-e' not set
1138 if [[ ! "$optionEncrypt" = "true" ]] && [[ "$optionRecDir" = "true" ]]; then
80fbd111
SBS
1139 yell "ERROR:$fn: \\'-R\\' is set but \\'-e\\' is not set."; fi;
1140 vbm "STATUS:$fn:Finished magicParseRecipientDir() function.";
c5da633d 1141} # Update recPubKeysValid with argRecDir
c5da633d
SBS
1142magicSetScriptTTL() {
1143 #Desc: Sets script_TTL seconds from provided time_element string argument
1144 #Usage: magicSetScriptTTL [str time_element]
1145 #Input: arg1: string (Ex: scriptTTL_TE; "day" or "hour")
1146 #Output: var: scriptTTL (integer seconds)
1147 #Depends: timeUntilNextHour, timeUntilNextDay
80fbd111 1148 local fn argTimeElement
6fe054b4 1149
80fbd111
SBS
1150 # Save function name
1151 fn="${FUNCNAME[0]}";
1152
1153 vbm "STATUS:$fn:Starting magicSetScriptTTL() function.";
c5da633d
SBS
1154 argTimeElement="$1";
1155 if [[ "$argTimeElement" = "day" ]]; then
1156 # Set script lifespan to end at start of next day
1157 if ! scriptTTL="$(timeUntilNextDay)"; then # sets scriptTTL, then checks exit code
1158 if [[ "$scriptTTL" -eq 0 ]]; then
1159 ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
1160 else
80fbd111 1161 yell "ERROR:$fn:timeUntilNextDay exit code $?"; exit 1;
c5da633d
SBS
1162 fi;
1163 fi;
1164 elif [[ "$argTimeElement" = "hour" ]]; then
1165 # Set script lifespan to end at start of next hour
1166 if ! scriptTTL="$(timeUntilNextHour)"; then # sets scriptTTL, then checks exit code
1167 if [[ "$scriptTTL" -eq 0 ]]; then
1168 ((scriptTTL++)); # Add 1 because 0 would cause 'timeout' to never timeout.
1169 else
80fbd111 1170 yell "ERROR:$fn:timeUntilNextHour exit code $?"; exit 1;
c5da633d
SBS
1171 fi;
1172 fi;
1173 else
80fbd111 1174 yell "ERROR:$fn:Invalid argument for setScriptTTL function:$argTimeElement"; exit 1;
c5da633d 1175 fi;
80fbd111 1176 vbm "STATUS:$fn:Finished magicSetScriptTTL() function.";
c5da633d 1177} # Set scriptTTL in seconds until next (day|hour).
c5da633d
SBS
1178magicWriteVersion() {
1179 # Desc: Appends time-stamped VERSION to pathout_tar
1180 # Usage: magicWriteVersion
1181 # Input: vars: pathout_tar, dir_tmp
1182 # Input: vars: scriptVersion, scriptURL, ageVersion, ageURL, scriptHostname
1183 # Input: array: recPubKeysValid
1184 # Output: appends tar (pathout_tar)
1185 # Depends: bash 5.0.3, dateTimeShort(), appendArgTar()
80fbd111 1186 local fn fileoutVersion contentVersion pubKeyIndex pubKeyIndex
6fe054b4 1187
80fbd111
SBS
1188 # Save function name
1189 fn="${FUNCNAME[0]}";
1190
1191 vbm "STATUS:$fn:Starting magicWriteVersion() function.";
c5da633d
SBS
1192 # Set VERSION file name
1193 fileoutVersion="$(dateTimeShort)..VERSION";
1194
1195 # Gather VERSION data in contentVersion
1196 contentVersion="scriptVersion=$scriptVersion";
1197 #contentVersion="$contentVersion""\\n";
1198 contentVersion="$contentVersion""\\n""scriptName=$scriptName";
1199 contentVersion="$contentVersion""\\n""scriptURL=$scriptURL";
1200 contentVersion="$contentVersion""\\n""ageVersion=$ageVersion";
1201 contentVersion="$contentVersion""\\n""ageURL=$ageURL";
1202 contentVersion="$contentVersion""\\n""date=$(date --iso-8601=seconds)";
1203 contentVersion="$contentVersion""\\n""hostname=$scriptHostname";
1204 ## Add list of recipient pubkeys
1205 for pubkey in "${recPubKeysValid[@]}"; do
1206 ((pubKeyIndex++))
1207 contentVersion="$contentVersion""\\n""PUBKEY_$pubKeyIndex=$pubkey";
1208 done
1209 ## Process newline escapes
1210 contentVersion="$(echo -e "$contentVersion")"
1211
1212 # Write contentVersion as file fileoutVersion and write-append to pathout_tar
80fbd111
SBS
1213 appendArgTar "$contentVersion" "$fileoutVersion" "$pathout_tar" "$dir_tmp" && \
1214 vbm "STATUS:$fn:Appended $fileoutVersion to $pathout_tar";
1215 vbm "STATUS:$fn:Finished magicWriteVersion() function.";
c5da633d 1216} # write version data to pathout_tar via appendArgTar()
5938a598 1217magicProcessWriteBuffer() {
c5da633d
SBS
1218 # Desc: process and write buffer
1219 # In : vars: bufferTTL bufferTTL_STR scriptHostname label dir_tmp SECONDS
1220 # : arry: buffer
1221 # Out: file:(pathout_tar)
1222 # Depends: Bash 5.0.3, date 8.30, yell(), vbm(), dateTimeShort(),
1223 ### Note: These arrays should all have the same number of elements:
1224 ### pathouts, fileouts, procFileExts, procStrings
1225
1226 local fn timeBufferStartLong timeBufferStart fileoutBasename
1227 local -a fileouts pathouts
1228 local writeCmd1 writeCmd2 writeCmd3 writeCmd4
1229
c5da633d
SBS
1230 # Debug:Get function name
1231 fn="${FUNCNAME[0]}";
80fbd111
SBS
1232
1233 vbm "STATUS:$fn:Started magicProcessWriteBuffer().";
c5da633d
SBS
1234 # Determine file paths (time is start of buffer period)
1235 ## Calculate start time
1236 timeBufferStartLong="$(date --date="$bufferTTL seconds ago" --iso-8601=seconds)" && \
80fbd111 1237 vbm "DEBUG:$fn:timeBufferStartLong:$timeBufferStartLong";
c5da633d 1238 timeBufferStart="$(dateTimeShort "$timeBufferStartLong" )" && \
80fbd111 1239 vbm "DEBUG:$fn:timeBufferStart:$timeBufferStart"; # Note start time YYYYmmddTHHMMSS+zzzz (no separators)
c5da633d
SBS
1240 ## Set common basename
1241 fileoutBasename="$timeBufferStart""--""$bufferTTL_STR""..""$scriptHostname""$label" && \
80fbd111 1242 vbm "STATUS:$fn:Set fileoutBasename to:$fileoutBasename";
c5da633d
SBS
1243 ## Determine output file name array
1244 ### in: fileOutBasename cmd_compress_suffix cmd_encrypt_suffix procFileExts
1245 for fileExt in "${procFileExts[@]}"; do
1246 fileouts+=("$fileoutBasename""$fileExt""$cmd_compress_suffix""$cmd_encrypt_suffix") && \
80fbd111 1247 vbm "STATUS:$fn:Added $fileExt to fileouts:${fileouts[*]}";
c5da633d
SBS
1248 done;
1249 for fileName in "${fileouts[@]}"; do
1250 pathouts+=("$dir_tmp"/"$fileName") && \
80fbd111 1251 vbm "STATUS:$fn:Added $fileName to pathouts:${pathouts[*]}";
c5da633d
SBS
1252 done;
1253 ## Update pathout_tar
1254 magicInitCheckTar;
1255
1256 # Process and write buffers to dir_tmp
1257 ## Prepare command strings
1258 writeCmd1="printf \"%s\\\\n\" \"\${buffer[@]}\""; # printf "%s\\n" "${buffer[@]}"
1259 #writeCmd2="" # NOTE: Specified by parsing array procStrings
1260 writeCmd3="$cmd_compress";
1261 writeCmd4="$cmd_encrypt";
1262
1263 ## Process buffer and write to dir_tmp
1264 for index in "${!pathouts[@]}"; do
21363b80
SBS
1265 writeCmd2="${procStrings[$index]}";
1266 writeCmdAll="$writeCmd1 | $writeCmd2 | $writeCmd3 | $writeCmd4" && vbm "STATUS:$fn:Assembled command:\"$writeCmdAll\"";
1267 eval "$writeCmdAll" >> "${pathouts[$index]}" && vbm "STATUS:$fn:Wrote command output to ${pathouts[$index]}";
c5da633d
SBS
1268 done;
1269
1270 # Append dir_tmp files to pathout_tar
1271 wait; # Wait to avoid collision with older magicProcessWriteBuffer() instances (see https://www.tldp.org/LDP/abs/html/x9644.html )
1272 for index in "${!pathouts[@]}"; do
21363b80
SBS
1273 appendFileTar "${pathouts[$index]}" "${fileouts[$index]}" "$pathout_tar" "$dir_tmp" && \
1274 vbm "STATUS:$fn:Appended ${pathouts[$index]} to $pathout_tar";
c5da633d
SBS
1275 done;
1276
1277 # Remove secured chunks from dir_tmp
1278 for path in "${pathouts[@]}"; do
21363b80 1279 rm "$path" && vbm "STATUS:$fn:Removed:$path";
c5da633d
SBS
1280 done;
1281
80fbd111 1282 vbm "STATUS:$fn:Finished magicProcessWriteBuffer().";
5938a598 1283} # Process and Write buffer
4e4707fb 1284
5938a598 1285main() {
80fbd111
SBS
1286 # Desc: Main function
1287 # Usage: main "$@"
1288 # Inputs: many
1289 # Outputs: file (pathout_tar)
1290 # Depends: many
1291 local fn
1292
1293 # Debug:Get function name
1294 fn="${FUNCNAME[0]}";
1295
1296 vbm "STATUS:$fn:Started function main().";
c5da633d
SBS
1297 # Process arguments
1298 processArguments "$@";
1299 ## Determine working directory
1300 magicInitWorkingDir; # Sets dir_tmp from argTempDirPriority
1301 ## Set output encryption and compression option strings
1302 ### React to "-e" and "-r" ("encryption recipients") options
1303 magicParseRecipientArgs; # Updates recPubKeysValid, cmd_encrypt[_suffix] from argRecPubKeys
1304 ### React to "-R" ("recipient directory") option
1305 magicParseRecipientDir; # Updates recPubKeysValid
1306 ### React to "-c" ("compression") option
1307 magicParseCompressionArg; # Updates cmd_compress[_suffix]
1308 ## React to "-b" and "-B" (custom buffer and script TTL) options
1309 magicParseCustomTTL; # Sets custom scriptTTL_TE and/or bufferTTL if specified
1310 ## React to "-p" (user-supplied process command and file extension strings) options
1311 magicParseProcessStrings; # Sets arrays: procStrings, procFileExts
1312 ## React to "-l" (output file label) option
1313 magicParseLabel; # sets label (ex: "_location")
c5da633d
SBS
1314
1315 # Perform secondary setup operations
1316 ## Set script lifespan (scriptTTL from scriptTTL_TE)
1317 magicSetScriptTTL "$scriptTTL_TE";
1318 ## File name substring (ISO-8601 duration from bufferTTL)
80fbd111 1319 bufferTTL_STR="$(timeDuration "$bufferTTL")" && vbm "DEBUG:$fn:bufferTTL_STR:$bufferTTL_STR";
c5da633d 1320 ## Init temp working dir
80fbd111 1321 try mkdir "$dir_tmp" && vbm "DEBUG:$fn:Working dir created at dir_tmp:$dir_tmp";
c5da633d
SBS
1322 ## Initialize output tar (set pathout_tar)
1323 magicInitCheckTar;
1324
1325 # Check vital apps, files, dirs
1326 if ! checkapp tar && ! checkdir "$dirOut" "dir_tmp"; then
80fbd111 1327 yell "ERROR:$fn:Critical components missing.";
c5da633d
SBS
1328 displayMissing; yell "Exiting."; exit 1; fi
1329
1330 # MAIN LOOP: Run until script TTL seconds pass
5938a598 1331 bufferRound=0;
5938a598 1332 while [[ $SECONDS -lt "scriptTTL" ]]; do
80fbd111 1333 vbm "STATUS:$fn:Starting buffer round:$bufferRound";
c5da633d 1334 bufferTOD="$((SECONDS + bufferTTL))"; # Set buffer round time-of-death
5938a598
SBS
1335 # Consume stdin to fill buffer until buffer time-of-death (TOD) arrives
1336 while read -r -t "$bufferTTL" line && [[ $SECONDS -lt "$bufferTOD" ]]; do
1337 # Append line to buffer array
1338 buffer+=("$line");
5938a598 1339 done;
c5da633d 1340 # Create dir_tmp if missing
80fbd111
SBS
1341 if ! [[ -d "$dir_tmp" ]]; then
1342 yell "ERROR:$fn:dir_tmp existence failure:$dir_tmp";
1343 try mkdir "$dir_tmp" && vbm "DEBUG:$fn:Working dir recreated dir_tmp:$dir_tmp"; fi
c5da633d
SBS
1344 # Update encryption recipient array
1345 magicParseRecipientDir; # Update recPubKeysValid with argRecDir
5938a598
SBS
1346 # Export buffer to asynchronous processing.
1347 magicProcessWriteBuffer &
1348 unset buffer; # Clear buffer array for next bufferRound
1349 # Increment buffer round
1350 ((bufferRound++));
1351 done;
c5da633d
SBS
1352
1353 # Cleanup
1354 ## Remove dir_tmp
80fbd111 1355 try rm -r "$dir_tmp" && vbm "STATUS:$fn:Removed dir_tmp:$dir_tmp";
c5da633d 1356
80fbd111 1357 vbm "STATUS:$fn:Finished function main().";
c5da633d 1358} # Main function
6cbe7c0a 1359
5938a598
SBS
1360#===END Declare local script functions===
1361#==END Define script parameters==
1362
1363#==BEGIN Perform work and exit==
1364main "$@" # Run main function.
1365exit 0;
1366#==END Perform work and exit==
1367
1368# Author: Steven Baltakatei Sandoval;
6cbe7c0a 1369# License: GPLv3+