feat(user/bkotslu):Retrieve OTS files from storage if older
[BK-2020-03.git] / user / bkotslu
CommitLineData
aa8be4bf
SBS
1#!/bin/bash
2# Desc: Utility for backing up and retrieving ots files
df7774c1 3# Version: 0.0.2
aa8be4bf
SBS
4
5OTS_FCACHE_DIR="$HOME/.cache/bkotslu/";
6MAX_FIND_DEPTH=12;
7
8yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
9die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
10must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
11vbm() {
12 # Description: Prints verbose message ("vbm") to stderr if opVerbose is set to "true".
13 # Usage: vbm "DEBUG :verbose message here"
14 # Version 0.2.0
15 # Input: arg1: string
16 # vars: opVerbose
17 # Output: stderr
18 # Depends: bash 5.1.16, GNU-coreutils 8.30 (echo, date)
19
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.
23 fi
24
25 # End function
26 return 0; # Function finished.
27}; # Displays message if opVerbose true
28showUsage() {
29 # Desc: Display script usage information
30 # Usage: showUsage
31 # Version 0.0.2
32 # Input: none
33 # Output: stdout
34 # Depends: GNU-coreutils 8.30 (cat)
35 cat <<'EOF'
36 USAGE:
37 bkotslu [ options ] [FILE...]
38
39 OPTIONS:
40 -h, --help
41 Display help information.
42 --version
43 Display script version.
44 -v, --verbose
45 Display debugging info.
46 -i, --input-file
47 Define input file path for file to add ots file to.
48 -O, --output-dir
49 Define output directory path for storing ots backup files.
50 DEFAULT: $HOME/.cache/bkotslu/
51 --
52 Indicate end of options.
53
54 EXAMPLE:
55 Hash foo.txt and lookup matching ots file
56 bkotslu -i foo.txt
57
58 Archive ots files from home directory
59 bkotslu -I $HOME/
60
61EOF
62}; # Display information on how to use this script.
63showVersion() {
64 # Desc: Displays script version and license information.
65 # Usage: showVersion
66 # Version: 0.0.2
67 # Input: scriptVersion var containing version string
68 # Output: stdout
69 # Depends: vbm(), yell, GNU-coreutils 8.30
70
71 # Initialize function
72 vbm "DEBUG:showVersion function called."
73
74 cat <<'EOF'
75bkotslu 0.0.1
76Copyright (C) 2024 Steven Baltakatei Sandoval
77License GPLv3: GNU GPL version 3
78This is free software; you are free to change and redistribute it.
79There is NO WARRANTY, to the extent permitted by law.
80
81 GNU Coreutils 8.32
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.
86EOF
87
88 # End function
89 vbm "DEBUG:showVersion function ended."
90 return 0; # Function finished.
91}; # Display script version.
df7774c1
SBS
92checkDepends() {
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;
100};
aa8be4bf
SBS
101processArgs() {
102 # Desc: Processes arguments provided to script.
103 # Usage: processArgs "$@"
104 # Version: 1.0.0
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
112 # Depends:
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
119 # Ref./Attrib.:
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
122
123 # Initialize function
124 vbm "DEBUG:processArgs function called."
125
126 # Perform work
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].
131 case "$1" in
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.
137 pathFileIn1="$2";
138 vbm "DEBUG:Input file pathFileIn1 set to:${pathFileIn1}";
139 shift;
140 else
141 die "FATAL: Specified input file does not exist:$2";
142 fi;;
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.
145 pathDirIn1="$2";
146 vbm "DEBUG:Input directory pathDirIn1 set to:${pathDirIn1}";
147 shift;
148 else # Display error if $2 is not a valid dir.
149 die "FATAL:Specified input directory does not exist:$2";
150 fi;;
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
153 pathDirOut1="$2";
154 vbm "DEBUG:Output directory pathDirOut1 set to:${pathDirOut1}";
155 shift;
156 else
157 die "FATAL:Specified output directory is not valid:$2";
158 fi;;
159 --) # End of all options. See [2].
160 shift;
161 for arg in "$@"; do
162 vbm "DEBUG:adding to arrayPosArgs:$arg";
163 arrayPosArgs+=("$arg");
164 done;
165 break;;
166 -*) showUsage; die "FATAL: Unrecognized option.";; # Display usage
167 *) showUsage; die "FATAL: Unrecognized argument.";; # Handle unrecognized options. See [1].
168 esac;
169 shift;
170 done;
171
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}";
177 fi;
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";
182 fi;
183
184 # End function
185 vbm "DEBUG:processArgs function ended.";
186 return 0; # Function finished.
187}; # Evaluate script options from positional arguments (ex: $1, $2, $3, etc.).
188get_ots_filehash() {
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
194 # BK-2020-03 yell()
195 # Input: arg1 OTS file path
196 # Output: stdout sha256 file hash (lowercase)
197 local output;
aa8be4bf 198 vbm "DEBUG:Starting get_ots_filehash() on:$1";
df7774c1 199
aa8be4bf
SBS
200 if output="$( "$(which ots)" info "$1" | \
201 grep -E "^File sha256 hash: " | \
202 head -n1 | \
203 sed -E -e 's/(^File sha256 hash: )([0-9a-f]+$)/\2/g'; )" && \
204 [[ -n "$output" ]]; then
df7774c1 205 vbm "STATUS:Read file digest (${output}) via ots from:$1";
aa8be4bf
SBS
206 printf "%s" "$output";
207 return 0;
208 else
209 yell "ERROR:Encountered problem getting file hash via ots from:$1";
210 return 1;
211 fi;
212}; # Gets hash of file from ots file
213get_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
220 # vbm()
221 # BK-2020-03: yell()
222 local output;
df7774c1
SBS
223 vbm "DEBUG:Starting get_ots_oldestblock() on:$1";
224
aa8be4bf
SBS
225 if output="$( "$(which ots)" info "$1" | \
226 grep -E "verify BitcoinBlockHeaderAttestation\([0-9]+\)" | \
227 sort | head -n1 | \
228 sed -E -e 's/(^ verify BitcoinBlockHeaderAttestation)\(([0-9]+)(\))/\2/g'; )" && \
229 [[ -n "$output" ]]; then
df7774c1 230 vbm "STATUS:Retrieved Bitcoin block (${output}) via ots from:$1";
aa8be4bf
SBS
231 printf "%s" "$output";
232 return 0;
233 else
234 yell "ERROR:Encountered problem getting Bitcoin block number via ots from:$1";
235 return 1;
236 fi;
237}; # Gets oldest Bitcoin block from ots file
238cache_ots_file() {
239 # Desc: Scans and caches an OTS file for storing
df7774c1
SBS
240 # Input: arg1 input file
241 # Output: exit code
242 local fin="$1";
243 vbm "STATUS:Starting cache_ots_file()";
244
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
aa8be4bf
SBS
248 fout="${fhash}_${block}.otsu"; # file name out
249 pout="${pathDirOut1}/${fout}"; # file path out
df7774c1
SBS
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";
253 return 0;
aa8be4bf 254 else
df7774c1
SBS
255 yell "ERROR:Problem analyzing file with OpenTimestamps:${fin}";
256 return 1;
aa8be4bf
SBS
257 fi;
258}; # Scans a single OTS file
df7774c1
SBS
259get_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/';
265};
266get_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()";
272
273 digest="$1";
274 if output="$( find "$pathDirOut1" -type f -name "${digest}*" | \
275 sort | \
276 head -n1 )" && \
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";
281 return 0;
282 else
283 yell "ERROR:Could not find matching OTS file with digest ${digest} in ${pathDirOut1}";
284 return 1;
285 fi;
286};
aa8be4bf 287main() {
df7774c1 288 checkDepends;
aa8be4bf 289 processArgs "$@";
df7774c1 290 vbm "DEBUG:Starting rest of main()";
aa8be4bf
SBS
291
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")";
df7774c1 296 cache_ots_file "$line";
aa8be4bf
SBS
297 done < <(find "$pathDirIn1" -maxdepth "$MAX_FIND_DEPTH" -type f -name "*.ots");
298 fi;
299
300 # Lookup OTS file from archive for provided file.
301 if [[ -n "$pathFileIn1" ]]; then
df7774c1
SBS
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.";
306 fi;
307
308 # Get file hash
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";
322 else
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}";
324 fi;
325 else
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";
328 fi;
329 else
330 vbm "STATUS:No stored OTS found. No action taken for:${pathFileIn1}";
331 fi;
aa8be4bf 332 fi;
aa8be4bf
SBS
333
334}; # main program
335
336main "$@";