2 # Desc: Utility for backing up and retrieving ots files
5 OTS_FCACHE_DIR
="$HOME/.cache/bkotslu/";
8 yell
() { echo "$0: $*" >&2; } # print script path and all args to stderr
9 die
() { yell
"$*"; exit 111; } # same as yell() but non-zero exit status
10 must
() { "$@" || die
"cannot $*"; } # runs args as command, reports args if command fails
12 # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true".
13 # Usage: vbm "DEBUG :verbose message here"
18 # Depends: bash 5.1.16, GNU-coreutils 8.30 (echo, date)
20 if [ "$opVerbose" = "true" ]; then
21 functionTime
="$(date --iso-8601=ns)"; # Save current time in nano seconds.
22 echo "[$functionTime]:$0:""$*" 1>&2; # Display argument text.
26 return 0; # Function finished.
27 }; # Displays message if opVerbose true
29 # Desc: Display script usage information
34 # Depends: GNU-coreutils 8.30 (cat)
37 bkotslu [ options ] [FILE...]
41 Display help information.
43 Display script version.
45 Display debugging info.
47 Define input file path for file to add ots file to.
49 Define output directory path for storing ots backup files.
50 DEFAULT: $HOME/.cache/bkotslu/
52 Indicate end of options.
55 Hash foo.txt and lookup matching ots file
58 Archive ots files from home directory
62 }; # Display information on how to use this script.
64 # Desc: Displays script version and license information.
67 # Input: scriptVersion var containing version string
69 # Depends: vbm(), yell, GNU-coreutils 8.30
72 vbm
"DEBUG:showVersion function called."
76 Copyright (C) 2024 Steven Baltakatei Sandoval
77 License GPLv3: GNU GPL version 3
78 This is free software; you are free to change and redistribute it.
79 There is NO WARRANTY, to the extent permitted by law.
82 Copyright (C) 2020 Free Software Foundation, Inc.
83 License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
84 This is free software: you are free to change and redistribute it.
85 There is NO WARRANTY, to the extent permitted by law.
89 vbm
"DEBUG:showVersion function ended."
90 return 0; # Function finished.
91 }; # Display script version.
93 vbm
"STATUS:Starting checkDepends()";
94 if ! command -v sha256sum
1>/dev
/random
2>&1; then
95 die
"FATAL:sha256sum not available."; fi;
96 if ! command -v grep 1>/dev
/random
2>&1; then
97 die
"FATAL:grep not available."; fi;
98 if ! command -v find 1>/dev
/random
2>&1; then
99 die
"FATAL:grep not available."; fi;
102 # Desc: Processes arguments provided to script.
103 # Usage: processArgs "$@"
105 # Input: "$@" (list of arguments provided to the function)
106 # Output: Sets following variables used by other functions:
107 # opVerbose Indicates verbose mode enable status. (ex: "true", "false")
108 # pathDirOut1 Path to output directory.
109 # pathDirIn1 Path to input directory.
110 # pathFileIn1 Path to input file.
111 # arrayPosArgs Array of remaining positional argments
113 # yell() Displays messages to stderr.
114 # vbm() Displays messsages to stderr if opVerbose set to "true".
115 # showUsage() Displays usage information about parent script.
116 # showVersion() Displays version about parent script.
117 # arrayPosArgs Global array for storing non-option positional arguments (i.e. arguments following the `--` option).
118 # External dependencies: bash (5.1.16), echo
120 # [1]: Marco Aurelio (2014-05-08). "echo that outputs to stderr". https://stackoverflow.com/a/23550347
121 # [2]: "Handling positional parameters" (2018-05-12). https://wiki.bash-hackers.org/scripting/posparams
123 # Initialize function
124 vbm
"DEBUG:processArgs function called."
127 if [ $# -le 0 ]; then yell
"FATAL:No arguments provided."; showUsage
; fi;
128 while [ ! $# -eq 0 ]; do # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
129 #yell "DEBUG:Starting processArgs while loop." # Debug stderr message. See [1].
130 #yell "DEBUG:Provided arguments are:""$*" # Debug stderr message. See [1].
132 -h |
--help) showUsage
; exit 1;; # Display usage.
133 --version) showVersion
; exit 1;; # Show version
134 -v |
--verbose) opVerbose
="true"; vbm
"DEBUG:Verbose mode enabled.";; # Enable verbose mode. See [1].
135 -i |
--input-file) # Define input file path
136 if [ -f "$2" ]; then # If $2 is file that exists, set pathFileIn1 to $2, pop $2.
138 vbm
"DEBUG:Input file pathFileIn1 set to:${pathFileIn1}";
141 die
"FATAL: Specified input file does not exist:$2";
143 -I |
--input-dir) # Define input directory path
144 if [ -d "$2" ]; then # If $2 is dir that exists, set pathDirIn1 to $2, pop $2.
146 vbm
"DEBUG:Input directory pathDirIn1 set to:${pathDirIn1}";
148 else # Display error if $2 is not a valid dir.
149 die
"FATAL:Specified input directory does not exist:$2";
151 -O |
--output-dir) # Define output directory path
152 if [ -d "$2" ]; then # If $2 is dir that exists, set pathDirOut1 to $2, pop $2
154 vbm
"DEBUG:Output directory pathDirOut1 set to:${pathDirOut1}";
157 die
"FATAL:Specified output directory is not valid:$2";
159 --) # End of all options. See [2].
162 vbm
"DEBUG:adding to arrayPosArgs:$arg";
163 arrayPosArgs
+=("$arg");
166 -*) showUsage
; die
"FATAL: Unrecognized option.";; # Display usage
167 *) showUsage
; die
"FATAL: Unrecognized argument.";; # Handle unrecognized options. See [1].
172 ## Identify ots file cache dir
173 if [[ -z "$pathDirOut1" ]]; then
174 vbm
"STATUS:No output directory for caching OTS files specified.";
175 pathDirOut1
="$OTS_FCACHE_DIR";
176 vbm
"STATUS:Assuming OTS files to be cached in:${pathDirOut1}";
178 ## Create cache dir if necessary
179 if [[ ! -d "$pathDirOut1" ]]; then
180 vbm
"STATUS:Creating OTS file cache directory:${pathDirOut1}";
181 must mkdir
-p "$pathDirOut1";
185 vbm
"DEBUG:processArgs function ended.";
186 return 0; # Function finished.
187 }; # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.).
189 # Desc: Gets hash of an opentimestamp'd file from ots file
190 # Usage: get_ots_filehash FILE
191 # Example: get_ots_filehash foo.txt.ots
192 # Depends: ots 0.7.0, GNU grep 3.7, GNU Coreutils 8.32
193 # vbm() verbose output
195 # Input: arg1 OTS file path
196 # Output: stdout sha256 file hash (lowercase)
198 vbm
"DEBUG:Starting get_ots_filehash() on:$1";
200 if output
="$( "$
(which ots
)" info "$1" | \
201 grep -E "^File sha256
hash: " | \
203 sed -E -e 's/(^File sha256 hash: )([0-9a-f]+$)/\2/g'; )" && \
204 [[ -n "$output" ]]; then
205 vbm
"STATUS:Read file digest (${output}) via ots from:$1";
206 printf "%s" "$output";
209 yell
"ERROR:Encountered problem getting file hash via ots from:$1";
212 }; # Gets hash of file from ots file
213 get_ots_oldestblock
() {
214 # Desc: Gets earliest Bitcoin block number from ots file
215 # Usage: get_ots_oldestblock FILE
216 # Example: get_ots_oldestblock foo.txt.ots
217 # Input: arg1 path OTS file path
218 # Output: stdout int Bitcoin block number
219 # Depends: OpenTimestamps 0.7.0, GNU grep 3.7, GNU Coreutils 8.32
223 vbm
"DEBUG:Starting get_ots_oldestblock() on:$1";
225 if output
="$( "$
(which ots
)" info "$1" | \
226 grep -E "verify BitcoinBlockHeaderAttestation\
([0-9]+\
)" | \
228 sed -E -e 's/(^ verify BitcoinBlockHeaderAttestation)\(([0-9]+)(\))/\2/g'; )" && \
229 [[ -n "$output" ]]; then
230 vbm
"STATUS:Retrieved Bitcoin block (${output}) via ots from:$1";
231 printf "%s" "$output";
234 yell
"ERROR:Encountered problem getting Bitcoin block number via ots from:$1";
237 }; # Gets oldest Bitcoin block from ots file
239 # Desc: Scans and caches an OTS file for storing
240 # Input: arg1 input file
243 vbm
"STATUS:Starting cache_ots_file()";
245 if [[ ! -f "$fin" ]]; then die
"FATAL:OTS file not found:$fin"; fi;
246 if fhash
="$(must get_ots_filehash "$fin")" && \
247 block
="$(must get_ots_oldestblock "$fin")"; then
248 fout
="${fhash}_${block}.otsu"; # file name out
249 pout
="${pathDirOut1}/${fout}"; # file path out
250 vbm
"STATUS:Found OTS file at ${fin} with hash ${fhash} and block ${block}.";
251 vbm
"STATUS:Saving found OTS file to ${pout}";
252 must
cp -n "$fin" "$pout";
255 yell
"ERROR:Problem analyzing file with OpenTimestamps:${fin}";
258 }; # Scans a single OTS file
259 get_sha256_digest
() {
260 # Depends: GNU Coreutils 8.32 (sha256sum)
261 # Input: arg1 path file path
262 # Output: stdout str sha256 digest (lowercase hexadecimal)
263 vbm
"DEBUG:Starting get_sha256_digest()";
264 sha256sum
"$1" |
head -n1 |
sed -E -e 's/(^[0-9a-f]{64})(.+)/\1/';
266 get_oldest_stored_ots_path
() {
267 # Desc: Lookup most recent OTS file from storage
268 # Input: pathDirOut1 var path to OTS storage dir
269 # arg1 str sha256 digest (lowercase hexadecimal)
270 # Output: stdout path OTS file with matching sha256 digest
271 vbm
"DEBUG:Starting get_oldest_stored_ots_path()";
274 if output
="$( find "$pathDirOut1" -type f -name "${digest}*" | \
277 [[ -n "$output" ]]; then
278 output
="$(readlink -f "$output")";
279 vbm
"STATUS:Found matching OTS file with digest ${digest} at:$output";
280 printf "%s" "$output";
283 yell
"ERROR:Could not find matching OTS file with digest ${digest} in ${pathDirOut1}";
290 vbm
"DEBUG:Starting rest of main()";
292 # Scan provided dir for OTS files to cache.
293 if [[ -n "$pathDirIn1" ]]; then
294 while read -r line
; do
295 line
="$(readlink -f "$line")";
296 cache_ots_file
"$line";
297 done < <(find "$pathDirIn1" -maxdepth "$MAX_FIND_DEPTH" -type f
-name "*.ots");
300 # Lookup OTS file from archive for provided file.
301 if [[ -n "$pathFileIn1" ]]; then
302 # Check for and store any OTS file attached to provided file
303 if [[ -f "${pathFileIn1}.ots" ]]; then
304 vbm
"STATUS:OTS file accompanying provided file found:${pathFileIn1}.ots";
305 cache_ots_file
"${pathFileIn1}.ots" && vbm
"STATUS:Stored provided file's OTS file.";
309 fhash
="$(get_sha256_digest "$pathFileIn1"; )";
310 if path_stored_ots
="$(get_oldest_stored_ots_path "$fhash"; )"; then
311 vbm
"STATUS:Stored OTS found with matching hash.";
312 blk_stored
="$(get_ots_oldestblock "$path_stored_ots"; )";
313 vbm
"STATUS:Stored OTS file has block number ${blk_stored}.";
314 if [[ -f "${pathFileIn1}.ots" ]]; then
315 vbm
"STATUS:An OTS file is next to provided file.";
316 blk_provid
="$(get_ots_oldestblock "${pathFileIn1}.ots
"; )";
317 vbm
"STATUS:Provided file's OTS file has block number ${blk_provid}";
318 if [[ "$blk_stored" -lt "$blk_provid" ]]; then
319 vbm
"STATUS:Older timestamp in OTS store found. Replacing ${pathFileIn1} (block ${blk_provid}) with ${path_stored_ots} (block ${blk_stored}).";
320 #must mv "$pathFileIn1" "${pathFileIn1}.baku";
321 #must cp "$path_stored_ots" "$pathFileIn1";
323 yell
"STATUS:Stored OTS file (block ${blk_stored}) is not older than provided file's OTS file (block ${blk_provid}). No action taken for:${pathFileIn1}";
326 vbm
"STATUS:Stored OTS file found with digest matching provided file. Copying ${path_stored_ots} to ${pathFileIn1}.ots";
327 must
cp "$path_stored_ots" "${pathFileIn1}.ots";
330 vbm
"STATUS:No stored OTS found. No action taken for:${pathFileIn1}";