+++ /dev/null
-#!/usr/bin/env bash
-# Desc: Create and sign a checksum
-# Input: stdin: file list (newline delimited)
-# arg(s): file paths (IFS delimited)
-# Output: file containing sha256 hashes and file paths
-# Depends: bash v5.1.16, date (GNU Coreutils 8.32), gpg v2.2.27, ots v0.7.0
-# Version: 0.0.1
-
-declare -Ag appRollCall # Associative array for storing app status
-declare -Ag fileRollCall # Associative array for storing file status
-declare -Ag dirRollCall # Associative array for storing dir status
-declare -ag arrPosArgs; # positional arguments
-declare -ag arrStdin; # standard input lines
-declare -ag arrInFiles; # input files
-
-yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
-die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
-try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
-vbm() {
- # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true".
- # Usage: vbm "DEBUG :verbose message here"
- # Version 0.2.0
- # Input: arg1: string
- # vars: opVerbose
- # Output: stderr
- # Depends: bash 5.1.16, GNU-coreutils 8.30 (echo, date)
-
- if [ "$opVerbose" = "true" ]; then
- functionTime="$(date --iso-8601=ns)"; # Save current time in nano seconds.
- echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text.
- fi
-
- # End function
- return 0; # Function finished.
-} # Displays message if opVerbose true
-showUsage() {
- # Desc: Display script usage information
- # Usage: showUsage
- # Version 0.0.2
- # Input: none
- # Output: stdout
- # Depends: GNU-coreutils 8.30 (cat)
- cat <<'EOF'
- USAGE:
- bksumsign.sh [ options ] [FILE...]
-
- DESCRIPTION:
- Creates sha256 checksum of files.
-
- OPTIONS:
- -h, --help
- Display help information.
- --version
- Display script version.
- -v, --verbose
- Display debugging info.
- -o, --output-file
- Define output file path. By default, the file
- name includes the full ISO-8601 timestamp
- without separators, e.g.:
- 20220920T2117+0000..SHA256SUM
- -s, --sign
- Sign with GnuPG the checksum file.
- -t, --timestamp
- Timestamp with OpenTimestamps the checksum file
- (and GnuPG signature if -s/--sign specified).
- --
- Indicate end of options.
-
- EXAMPLE:
- bksumsign.sh file.txt
- bksumsign.sh file1.txt file2.txt
- bksumsign.sh -v -- file1.txt file2.txt
- bksumsign.sh -v -t -- file1.txt file2.txt
- find . -type f | bksumsign.sh
- find . -type f | bksumsign.sh -v -s -t -- file.txt
-
- NOTE:
- If GNU Coreutils 8.32 `sha256sum` used, checksum file
- can be verified using:
- sha256sum --check 20220920T2117+0000..SHA256SUM
-EOF
-} # Display information on how to use this script.
-showVersion() {
- # Desc: Displays script version and license information.
- # Usage: showVersion
- # Version: 0.0.2
- # Input: scriptVersion var containing version string
- # Output: stdout
- # Depends: vbm(), yell, GNU-coreutils 8.30
-
- # Initialize function
- vbm "DEBUG:showVersion function called."
-
- cat <<'EOF'
-bksumsign 0.0.1
-Copyright (C) 2022 Steven Baltakatei Sandoval
-License GPLv3: GNU GPL version 3
-This is free software; you are free to change and redistribute it.
-There is NO WARRANTY, to the extent permitted by law.
-
-EOF
-
- # End function
- vbm "DEBUG:showVersion function ended."
- return 0; # Function finished.
-} # Display script version.
-checkapp() {
- # Desc: If arg is a command, save result in assoc array 'appRollCall'
- # Usage: checkapp arg1 arg2 arg3 ...
- # Version: 0.1.1
- # Input: global assoc. array 'appRollCall'
- # Output: adds/updates key(value) to global assoc array 'appRollCall'
- # Depends: bash 5.0.3
- local returnState
-
- #===Process Args===
- for arg in "$@"; do
- if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
- appRollCall[$arg]="true";
- if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
- else
- appRollCall[$arg]="false"; returnState="false";
- fi;
- done;
-
- #===Determine function return code===
- if [ "$returnState" = "true" ]; then
- return 0;
- else
- return 1;
- fi;
-} # Check that app exists
-checkfile() {
- # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
- # Usage: checkfile arg1 arg2 arg3 ...
- # Version: 0.1.2
- # Input: global assoc. array 'fileRollCall'
- # Output: adds/updates key(value) to global assoc array 'fileRollCall';
- # Output: returns 0 if app found, 1 otherwise
- # Depends: bash 5.0.3
- local returnState
-
- #===Process Args===
- for arg in "$@"; do
- if [ -f "$arg" ]; then
- fileRollCall["$arg"]="true";
- if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
- elif [ -z "$arg" ]; then
- fileRollCall["(no name)"]="false"; returnState="false";
- else
- fileRollCall["$arg"]="false"; returnState="false";
- fi;
- done;
-
- #===Determine function return code===
- if [ "$returnState" = "true" ]; then
- return 0;
- else
- return 1;
- fi;
-} # Check that file exists
-checkdir() {
- # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
- # Usage: checkdir arg1 arg2 arg3 ...
- # Version 0.1.2
- # Input: global assoc. array 'dirRollCall'
- # Output: adds/updates key(value) to global assoc array 'dirRollCall';
- # Output: returns 0 if all args are dirs; 1 otherwise
- # Depends: Bash 5.0.3
- local returnState
-
- #===Process Args===
- for arg in "$@"; do
- if [ -z "$arg" ]; then
- dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
- elif [ -d "$arg" ]; then
- dirRollCall["$arg"]="true";
- if ! [ "$returnState" = "false" ]; then returnState="true"; fi
- else
- dirRollCall["$arg"]="false"; returnState="false";
- fi
- done
-
- #===Determine function return code===
- if [ "$returnState" = "true" ]; then
- return 0;
- else
- return 1;
- fi
-} # Check that dir exists
-displayMissing() {
- # Desc: Displays missing apps, files, and dirs
- # Usage: displayMissing
- # Version 1.0.0
- # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
- # Output: stderr: messages indicating missing apps, file, or dirs
- # Output: returns exit code 0 if nothing missing; 1 otherwise
- # Depends: bash 5, checkAppFileDir()
- local missingApps value appMissing missingFiles fileMissing
- local missingDirs dirMissing
-
- #==BEGIN Display errors==
- #===BEGIN Display Missing Apps===
- missingApps="Missing apps :";
- #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
- for key in "${!appRollCall[@]}"; do
- value="${appRollCall[$key]}";
- if [ "$value" = "false" ]; then
- #echo "DEBUG:Missing apps: $key => $value";
- missingApps="$missingApps""$key ";
- appMissing="true";
- fi;
- done;
- if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
- echo "$missingApps" 1>&2;
- fi;
- unset value;
- #===END Display Missing Apps===
-
- #===BEGIN Display Missing Files===
- missingFiles="Missing files:";
- #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
- for key in "${!fileRollCall[@]}"; do
- value="${fileRollCall[$key]}";
- if [ "$value" = "false" ]; then
- #echo "DEBUG:Missing files: $key => $value";
- missingFiles="$missingFiles""$key ";
- fileMissing="true";
- fi;
- done;
- if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
- echo "$missingFiles" 1>&2;
- fi;
- unset value;
- #===END Display Missing Files===
-
- #===BEGIN Display Missing Directories===
- missingDirs="Missing dirs:";
- #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
- for key in "${!dirRollCall[@]}"; do
- value="${dirRollCall[$key]}";
- if [ "$value" = "false" ]; then
- #echo "DEBUG:Missing dirs: $key => $value";
- missingDirs="$missingDirs""$key ";
- dirMissing="true";
- fi;
- done;
- if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
- echo "$missingDirs" 1>&2;
- fi;
- unset value;
- #===END Display Missing Directories===
-
- #==END Display errors==
- #==BEGIN Determine function return code===
- if [ "$appMissing" == "true" ] || [ "$fileMissing" == "true" ] || [ "$dirMissing" == "true" ]; then
- return 1;
- else
- return 0;
- fi
- #==END Determine function return code===
-} # Display missing apps, files, dirs
-processArgs() {
- # Desc: Processes arguments provided to script
- # Usage: processArgs "$@"
- # Version: 1.0.0 (modified)
- # Input: "$@" (list of arguments provided to the function)
- # Output: Sets following variables used by other functions:
- # opVerbose Indicates verbose mode enable status. (ex: "true", "false")
- # pathDirOut1 Path to output directory.
- # pathFileOut1 Path to output file.
- # opFileOut1_overwrite Indicates whether file pathFileOut1 should be overwritten (ex: "true", "false").
- # arrPosArgs Array of remaining positional argments
- # Depends:
- # yell() Displays messages to stderr.
- # vbm() Displays messsages to stderr if opVerbose set to "true".
- # showUsage() Displays usage information about parent script.
- # showVersion() Displays version about parent script.
- # arrPosArgs Global array for storing non-option positional arguments (i.e. arguments following the `--` option).
- # External dependencies: bash (5.1.16), echo
- # Ref./Attrib.:
- # [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347
- # [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams
-
- # Initialize function
- vbm "DEBUG:processArgs function called."
-
- # Perform work
- while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
- #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1].
- #yell "DEBUG:Provided arguments are:""$*" # Debug stderr message. See [1].
- case "$1" in
- -h | --help) showUsage; exit 1;; # Display usage.
- --version) showVersion; exit 1;; # Show version
- -v | --verbose) opVerbose="true"; vbm "DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1].
- -o | --output-file) # Define output file path
- if [ -f "$2" ]; then # If $2 is file that exists, prompt user to continue to overwrite, set pathFileOut1 to $2, pop $2.
- yell "Specified output file $2 already exists. Overwrite? (y/n):"
- read -r m; case $m in
- y | Y | yes) opFileOut1_overwrite="true";;
- n | N | no) opFileOut1_overwrite="false";;
- *) yell "Invalid selection. Exiting."; exit 1;;
- esac
- if [ "$opFileOut1_overwrite" == "true" ]; then
- pathFileOut1="$2";
- shift;
- vbm "DEBUG:Output file pathFileOut1 set to:""$2";
- else
- yell "ERORR:Exiting in order to not overwrite output file:""$pathFileOut1";
- exit 1;
- fi
- else
- pathFileOut1="$2";
- shift;
- vbm "DEBUG:Output file pathFileOut1 set to:""$2";
- fi ;;
- -s | --sign) # sign with gpg
- opSign="true"; vbm "DEBUG:Signing mode enabled.";;
- -t | --timestamp) # timestamp with ots
- opTimestamp="true"; vbm "DEBUG:Timestamp mode enabled.";;
- --) # End of all options. See [2].
- shift;
- for arg in "$@"; do
- vbm "DEBUG:adding to arrPosArgs:$arg";
- arrPosArgs+=("$arg");
- done;
- break;;
- -*) showUsage; yell "ERROR: Unrecognized option."; exit 1;; # Display usage
- *)
- for arg in "$@"; do
- vbm "DEBUG:adding to arrPosArgs:$arg";
- arrPosArgs+=("$arg");
- done;
- break;;
- esac;
- shift;
- done;
-
- # End function
- vbm "DEBUG:processArgs function ended."
- return 0; # Function finished.
-} # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.).
-processStdin() {
- # Desc: Save stdin lines to array
- # Input: stdin standard input
- # arrStdin array for storing stdin lines
- # Output: arrStdin array for storing stdin lines
- # Ref/Attrib: [1] https://unix.stackexchange.com/a/484643 Check if no command line arguments and STDIN is empty
-
- if [[ -t 0 ]]; then
- return 1; # error if file descriptor 0 (stdin) open
- else
- while read -r line; do
- arrStdin+=("$line"); done;
- return 0;
- fi;
-}; # Save stdin to array
-checkInput() {
- # Desc: Check that files (specified by positional arguments and
- # standard input lines) exist and are in the same directory.
- # Input: arrPosArgs[@] positional arguments
- # arrStdin[@] standard input lines
- # Output: return code 0 success
- # return code 1 failure
- # arrInFiles list of verified files
- # Depends: displayMissing(), checkfile();
- local flagMissing flagDirErr workDir;
-
- # Check that positional arguments are files
- for elem in "${arrPosArgs[@]}"; do
- if checkfile "$elem"; then
- arrInFiles+=("$elem");
- else
- flagMissing="true";
- fi;
- done;
-
- # Check that stdin lines are files
- for elem in "${arrStdin[@]}"; do
- if checkfile "$elem"; then
- arrInFiles+=("$elem");
- else
- flagMissing="true";
- fi;
- done;
-
- # Check that all files are in the same directory
- if [[ "$flagMissing" != "true" ]]; then
- workDir="$( dirname "$( readlink -f "${arrInFiles[0]}" )" )";
- for elem in "${arrInFiles[@]}"; do
- elem="$(readlink -f "$elem")"; # full path
- if [[ "$(dirname "$elem")" != "$workDir" ]]; then
- flagDirErr="true";
- fi;
- done;
- fi;
-
- # Return non-zero if displayMissing() reports files missing.
- if [[ "$flagMissing" == "true" ]]; then
- displayMissing; return 1; fi;
- if [[ "$flagDirErr" == "true" ]]; then
- yell "ERROR:All files not in same directory.";
- return 1; fi;
- return 0;
-}; # Check positional arguments
-checkDepends() {
- # Desc: Check if expected commands available
-
- checkapp date sha256sum;
- if [[ $opSign == "true" ]]; then checkapp gpg; fi;
- if [[ $opTimestamp == "true" ]]; then checkapp ots; fi;
-
- # Return failure is displayMissing() reports apps missing.
- if ! displayMissing; then return 1; else return 0; fi;
-}; # Check dependencies
-main() {
- # Input: pathFileOut1 option-specified output file path
- # appRollCall assoc-array for checkapp(), displayMissing()
- # fileRollCall assoc-array for checkfile(), displayMissing()
- # dirRollCall assoc-array for checkdir(), displayMissing()
- # arrPosArgs array for processArgs()
- # arrStdin array for processStdin()
- # arrInFiles array for checkInput()
- # (args) for processArgs()
- # (stdin) for processStdin()
- # Output: file written to $pathSumOut
- # file written to $pathSigOut
- #
- local fileSumOut dirOut pathSumOut pathSigOut;
-
- # Check dependencies and input
- if ! checkDepends; then die "FATAL:Missing apps."; fi;
- processArgs "$@";
- processStdin;
- vbm "DEBUG:$(declare -p arrPosArgs)";
- vbm "DEBUG:$(declare -p arrStdin)";
- if ! [[ "${#arrPosArgs[@]}" -ge 1 || "${#arrStdin[@]}" -ge 1 ]]; then
- yell "ERROR:No positional arguments or stdin lines.";
- showUsage; exit 1; fi;
- if ! checkInput; then die "FATAL:Invalid file list."; fi;
- vbm "DEBUG:$(declare -p arrInFiles)";
-
- # Do work
- if [[ -n "$pathFileOut1" ]]; then
- pathSumOut="$pathFileOut1";
- else
- dirOut="$( dirname "${arrInFiles[0]}" )";
- fileSumOut="$(date +%Y%m%dT%H%M%S%z)..SHA256SUMS";
- pathSumOut="$dirOut"/"$fileSumOut";
- fi;
- pathSigOut="$pathSumOut".asc;
- for file in "${arrInFiles[@]}"; do
- sha256sum "$file" >> "$pathSumOut";
- done;
-
- # Do optional work
- ## Sign checksum file.
- if [[ $opSign == "true" ]]; then
- try gpg --detach-sign --armor --output "$pathSigOut" "$pathSumOut";
- fi;
- ## Timestamp checksum file.
- if [[ $opTimestamp == "true" ]]; then
- try ots s "$pathSumOut"; fi;
-
- ## Timestamp checksum signature file.
- if [[ $opTimestamp == "true" && $opSign == "true" ]]; then
- try ots s "$pathSigOut";
- fi;
-
- return 0;
-}; # main program
-
-main "$@";
-
-# Author: Steven Baltakatei Sandoval
-# License: GPLv3+