-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathutils.sh
410 lines (344 loc) · 17.4 KB
/
utils.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
#!/usr/bin/env bash
#
# __ __
# / /____ ___ ____ ___ ___ _/ / This script is provided to you by https://github.com/tegonal/gt
# / __/ -_) _ `/ _ \/ _ \/ _ `/ / Copyright 2022 Tegonal Genossenschaft <[email protected]>
# \__/\__/\_, /\___/_//_/\_,_/_/ It is licensed under European Union Public License v. 1.2
# /___/ Please report bugs and contribute back your improvements
#
# Version: v1.2.0-SNAPSHOT
####### Description #############
#
# internal utility functions
# no backward compatibility guarantees or whatsoever
#
###################################
set -euo pipefail
shopt -s inherit_errexit
unset CDPATH
if ! [[ -v dir_of_gt ]]; then
dir_of_gt="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)"
readonly dir_of_gt
fi
if ! [[ -v dir_of_tegonal_scripts ]]; then
dir_of_tegonal_scripts="$dir_of_gt/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
fi
sourceOnce "$dir_of_tegonal_scripts/utility/date-utils.sh"
sourceOnce "$dir_of_tegonal_scripts/utility/gpg-utils.sh"
sourceOnce "$dir_of_tegonal_scripts/utility/io.sh"
sourceOnce "$dir_of_tegonal_scripts/utility/parse-fn-args.sh"
function exitBecauseSigningKeyNotImported() {
local remote publicKeysDir gpgDir unsecureParamPatternLong signingKeyAsc
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(remote publicKeysDir gpgDir unsecureParamPatternLong signingKeyAsc)
parseFnArgs params "$@"
logError "%s not imported, you won't be able to pull files from the remote \033[0;36m%s\033[0m without using %s true\n" "$signingKeyAsc" "$remote" "$unsecureParamPatternLong"
printf >&2 "Alternatively, you can:\n- place the %s manually in %s or\n- setup a gpg store yourself at %s\n" "$signingKeyAsc" "$publicKeysDir" "$gpgDir"
deleteDirChmod777 "$gpgDir"
exit 1
}
function findAscInDir() {
local -r dir=$1
shift 1 || traceAndDie "could not shift by 1"
find "$dir" -maxdepth 1 -type f -name "*.asc" "$@"
}
function checkWorkingDirExists() {
local workingDirAbsolute=$1
shift 1 || traceAndDie "could not shift by 1"
local workingDirParamPattern
source "$dir_of_gt/common-constants.source.sh" || traceAndDie "could not source common-constants.source.sh"
if ! [[ -d $workingDirAbsolute ]]; then
logError "working directory \033[0;36m%s\033[0m does not exist" "$workingDirAbsolute"
echo >&2 "Check for typos and/or use $workingDirParamPattern to specify another"
return 9
fi
}
function exitIfWorkingDirDoesNotExist() {
# shellcheck disable=SC2310 # we are aware of that || will disable set -e for checkWorkingDirExists
checkWorkingDirExists "$@" || exit $?
}
function exitIfRemoteDirDoesNotExist() {
local workingDirAbsolute remote
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote)
parseFnArgs params "$@"
local remoteDir
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
if ! [[ -d $remoteDir ]]; then
logError "remote \033[0;36m%s\033[0m does not exist, check for typos.\nFollowing the remotes which exist:" "$remote"
sourceOnce "$dir_of_gt/gt-remote.sh"
gt_remote_list -w "$workingDirAbsolute"
exit 9
fi
}
function invertBool() {
local b=$1
shift 1 || traceAndDie "could not shift by 1"
if [[ $b == true ]]; then
echo "false"
else
echo "true"
fi
}
function gitDiffChars() {
local hash1 hash2
hash1=$(git hash-object -w --stdin <<<"$1") || traceAndDie "cannot calculate hash for string: %" "$1"
hash2=$(git hash-object -w --stdin <<<"$2") || traceAndDie "cannot calculate hash for string: %" "$2"
shift 2 || traceAndDie "could not shift by 2"
git --no-pager diff "$hash1" "$hash2" \
--word-diff=color --word-diff-regex . --ws-error-highlight=all |
grep -A 1 @@ | tail -n +2
}
function initialiseGitDir() {
local workingDirAbsolute remote
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote)
parseFnArgs params "$@"
local repo gitconfig
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
mkdir -p "$repo" || die "could not create the repo at %s" "$repo"
git --git-dir="$repo/.git" init || die "could not git init the repo at %s" "$repo"
}
function reInitialiseGitDir() {
initialiseGitDir "$@"
cp "$gitconfig" "$repo/.git/config" || die "could not copy %s to %s" "$gitconfig" "$repo/.git/config"
}
function askToDeleteAndReInitialiseGitDirIfRemoteIsBroken() {
local workingDirAbsolute remote
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote)
parseFnArgs params "$@"
local repo gitconfig
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
if ! git --git-dir="$repo/.git" remote | grep "$remote" >/dev/null; then
logError "looks like the .git directory of remote \033[0;36m%s\033[0m is broken. There is no remote %s set up in its gitconfig. Following the remotes:" "$remote" "$remote"
git --git-dir="$repo/.git" remote
if [[ -f $gitconfig ]]; then
if askYesOrNo "Shall I delete the repo and re-initialise it based on %s" "$gitconfig"; then
deleteDirChmod777 "$repo"
reInitialiseGitDir "$workingDirAbsolute" "$remote"
else
exit 1
fi
else
logInfo >&2 "%s does not exist, cannot ask to re-initialise the repo, must abort" "$gitconfig"
exit 1
fi
fi
}
function reInitialiseGitDirIfDotGitNotPresent() {
local workingDirAbsolute remote
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote)
parseFnArgs params "$@"
local repo
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
if ! [[ -d "$repo/.git" ]]; then
logInfo "repo directory (or its .git directory) does not exist for remote \033[0;36m%s\033[0m. We are going to re-initialise it based on the stored gitconfig" "$remote"
reInitialiseGitDir "$workingDirAbsolute" "$remote"
else
askToDeleteAndReInitialiseGitDirIfRemoteIsBroken "$workingDirAbsolute" "$remote"
fi
}
function initialiseGpgDir() {
local -r gpgDir=$1
shift 1 || traceAndDie "could not shift by 1"
mkdir -p "$gpgDir" || die "could not create the gpg directory at %s" "$gpgDir"
# it's OK if we are not able to set the rights as we only use it temporary. This will cause warnings by gpg
# so the user could be aware of that something went wrong
chmod 700 "$gpgDir" || true
}
function latestRemoteTagIncludingChecks() {
local workingDirAbsolute remote tagFilter
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote tagFilter)
parseFnArgs params "$@"
local repo
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
local tagParamPattern
source "$dir_of_gt/common-constants.source.sh" || traceAndDie "could not source common-constants.source.sh"
logInfo >&2 "no tag provided via argument %s, will determine latest and use it instead" "$tagParamPattern"
local tag
tag=$(cd "$repo" && latestRemoteTag "$remote" "$tagFilter") ||
die "could not determine latest tag of remote \033[0;36m%s\033[0m with filter %s, need to abort as no tag was specified via argument %s either" "$remote" "$tagFilter" "$tagParamPattern"
logInfo >&2 "latest is \033[0;36m%s\033[0m honoring the tag filter %s" "$tag" "$tagFilter"
echo "$tag"
}
function validateSigningKeyAndImport() {
local remote sourceDir gpgDir publicKeysDir validateSigningKeyAndImport_callback autoTrust
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(remote sourceDir gpgDir publicKeysDir validateSigningKeyAndImport_callback autoTrust)
parseFnArgs params "$@"
exitIfArgIsNotFunction "$validateSigningKeyAndImport_callback" 4
local autoTrustParamPatternLong signingKeyAsc
source "$dir_of_gt/common-constants.source.sh" || traceAndDie "could not source common-constants.source.sh"
local -r publicKey="$sourceDir/$signingKeyAsc"
local -r sigExtension="sig"
local -r sigFile="$publicKey.$sigExtension"
logInfo "Verifying if we trust %s\n" "$publicKey"
local confirm
confirm=$(invertBool "$autoTrust")
local verified=false
local importIt=false
if ! [[ -f $sigFile ]]; then
logWarning "There is no %s next to %s, cannot verify it" "$sigFile" "$publicKey"
else
# note we verify the signature of the public key based on the normal gpg dir
# i.e. not based on the gpg dir of the remote but of the user
# which means we trust the public key only if the user trusts the public key which created the sig
if gpg --verify "$sigFile" "$publicKey"; then
verified=true
confirm=false
# new line on purpose to separate output of verify
echo ""
# signature is valid but it could be that the gpg key was expired or even revoked by now
local keyData keyId
keyData=$(getSigningGpgKeyData "$sigFile") || die "could not get the key data of %s" "$sigFile"
keyId=$(extractGpgKeyIdFromKeyData "$keyData")
if isGpgKeyInKeyDataExpired "$keyData"; then
local expirationTimestamp
expirationTimestamp=$(extractExpirationTimestampFromKeyData "$keyData") || die "could not extract the expiration timestamp out of the key data:\n%" "$keyData"
expirationDate=$(timestampToDateTime "$expirationTimestamp") || die "was not able to convert the expiration timestamp %s to a date" "$expirationTimestamp"
if [[ $autoTrust == true ]]; then
logInfo "The key %s used to sign %s expired at %s, ignoring it since you specified % true" "$keyId" "$publicKey" "$expirationDate" "$autoTrustParamPatternLong"
importIt=true
else
logInfo "The key %s used to sign %s expired at %s" "$keyId" "$publicKey" "$expirationDate"
if askYesOrNo "The signature as such is OK and thus we assume you still trust it. Or would you like to take a closer look at the key %s?" "$keyId"; then
listSignaturesAndHighlightKey "$keyId"
if askYesOrNo "Do you want to trust %s seeing now more details of the key %s which signed it" "$signingKeyAsc" "$keyId"; then
importIt=true
else
importIt=false
fi
else
importIt=true
fi
if [[ $importIt == true ]]; then
logInfo "trust confirmed for %s -- signature verified (see further above) via expired key %s" "$publicKey" "$keyId"
fi
fi
elif isGpgKeyInKeyDataRevoked "$keyData"; then
# key was revoked, lets see if the signature was created before the revocation,
# if so, then we ask the user if they still trust it
local getSigCreationDate sigCreationTimestamp
getSigCreationDate=$(getSigCreationDate "$sigFile") || die "could not get the creation date of the signature %s" "$sigFile"
sigCreationTimestamp=$(dateToTimestamp "$getSigCreationDate") || die "was not able to convert the signature creation date %s to a timestamp" "$getSigCreationDate"
local revData revCreatedTimestamp revCreate
revData=$(getRevocationData "$keyId" "") || die "could ont get the revocation data for key %s" "$keyId"
revCreatedTimestamp=$(extractCreationTimestampFromRevocationData "$revData") || die "was not able to extract the revocation creation timestamp from the revocation information:\n%" "$revData"
revCreate=$(timestampToDateTime "$revCreatedTimestamp") || die "was not able to convert the revocation creation timestamp %s to a date" "$revCreatedTimestamp"
if ((sigCreationTimestamp < revCreatedTimestamp)); then
logWarning "The key %s used to sign the %s was revoked at %s.\nHowever, the signature was created before at %s. You should take a closer look at the key and the reason why it was revoked to decide if you trust the signature." "$keyId" "$publicKey" "$revCreate" "$getSigCreationDate"
printf "Press enter to see the signatures of %s (will be shown automatically after 20 seconds)\n\n" "$keyId"
read -t 20 -r || true
listSignaturesAndHighlightKey "$keyId"
if askYesOrNo "Do you want to trust the %s although the key %s signing it was revoked?" "$signingKeyAsc" "$keyId"; then
logInfo "trust confirmed for %s -- signature verified (see further above) via revoked key %s" "$publicKey" "$keyId"
importIt=true
else
importIt=false
fi
else
logError "The key %s used to sign the %s was revoked at %s and but the signature was created afterwards at %s -- i.e. we cannot trust it" "$keyId" "$signingKeyAsc" "$revCreate" "$getSigCreationDate"
importIt=false
fi
else
logInfo "trust confirmed for %s -- signature verified" "$publicKey"
importIt=true
fi
else
# new line on purpose to separate output of verify
echo ""
logWarning "gpg verification failed for signing key \033[0;36m%s\033[0m -- if you trust this repo, then import the public key which signed %s into your personal gpg store" "$publicKey" "$signingKeyAsc"
fi
fi
if [[ $verified != true ]]; then
if [[ $autoTrust == true ]]; then
logInfo "since you specified %s true, we trust it nonetheless. This can be a security risk" "$autoTrustParamPatternLong"
importIt=true
else
logInfo "You can still trust this repository via manual consent.\nIf you do, then the %s of this remote will be stored in the remote's gpg store (not in your personal store) located at:\n%s" "$signingKeyAsc" "$gpgDir"
if askYesOrNo "Do you want to proceed and take a look at the %s of remote %s to be able to decide if you trust it or not?" "$signingKeyAsc" "$remote"; then
importIt=true
else
echo "Decision: do not continue! Skipping this public key accordingly"
fi
fi
fi
local confirmationQuestion
if [[ $confirm == false ]]; then
confirmationQuestion=""
else
confirmationQuestion="The above key(s) will be used to verify the files you will pull from remote $remote, do you trust them?"
fi
if [[ $importIt == true ]] && echo "" && importGpgKey "$gpgDir" "$publicKey" "$confirmationQuestion"; then
"$validateSigningKeyAndImport_callback" "$publicKey" "$sigFile"
else
logInfo "deleting gpg key file $publicKey for security reasons"
rm "$publicKey" || die "was not able to delete the gpg key file \033[0;36m%s\033[0m, aborting" "$publicKey"
fi
}
function importRemotesPulledSigningKey() {
local defaultWorkingDir
source "$dir_of_gt/common-constants.source.sh" || traceAndDie "could not source common-constants.source.sh"
local workingDirAbsolute remote importRemotesPulledSigningKey_callback
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote importRemotesPulledSigningKey_callback)
parseFnArgs params "$@"
exitIfArgIsNotFunction "$importRemotesPulledSigningKey_callback" 3
local gpgDir publicKeysDir repo
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
# shellcheck disable=SC2317 # called by name
function importRemotesPublicKeys_importKeyCallback() {
local -r publicKey=$1
local -r sig=$2
shift 2 || traceAndDie "could not shift by 2"
mv "$publicKey" "$publicKeysDir/" || die "unable to move public key \033[0;36m%s\033[0m into public keys directory %s" "$publicKey" "$publicKeysDir"
mv "$sig" "$publicKeysDir/" || die "unable to move the public key's signature \033[0;36m%s\033[0m into public keys directory %s" "$sig" "$publicKeysDir"
"$importRemotesPulledSigningKey_callback" "$publicKey" "$sig"
}
validateSigningKeyAndImport "$remote" "$repo/$defaultWorkingDir" "$gpgDir" "$publicKeysDir" importRemotesPublicKeys_importKeyCallback false
deleteDirChmod777 "$repo/.gt" || logWarning "was not able to delete %s, please delete it manually" "$repo/.gt"
}
function determineDefaultBranch() {
local workingDirAbsolute remote
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote)
parseFnArgs params "$@"
local repo
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
git --git-dir "$repo/.git" remote show "$remote" | sed -n '/HEAD branch/s/.*: //p' ||
(
logWarning >&2 "was not able to determine default branch for remote \033[0;36m%s\033[0m, going to use main" "$remote"
echo "main"
)
}
function checkoutGtDir() {
local workingDirAbsolute remote branch defaultWorkingDir
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote branch defaultWorkingDir)
parseFnArgs params "$@"
local repo
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
git -C "$repo" fetch --depth 1 "$remote" "$branch" || die "was not able to \033[0;36mgit fetch\033[0m from remote \033[0;36m%s\033[0m" "$remote"
# execute as if we are inside repo as we want to checkout there, remove all folders
git -C "$repo" checkout "$remote/$branch" -- "$defaultWorkingDir" && find "$repo/$defaultWorkingDir" -maxdepth 1 -type d -not -path "$repo/$defaultWorkingDir" -exec rm -r {} \;
}
function exitIfRepoBrokenAndReInitIfAbsent() {
local workingDirAbsolute remote
# shellcheck disable=SC2034 # is passed by name to parseFnArgs
local -ra params=(workingDirAbsolute remote)
parseFnArgs params "$@"
local remoteDir repo
source "$dir_of_gt/paths.source.sh" || traceAndDie "could not source paths.source.sh"
if [[ -f $repo ]]; then
die "looks like the remote \033[0;36m%s\033[0m is broken there is a file at the repo's location: %s" "$remote" "$remoteDir"
else
reInitialiseGitDirIfDotGitNotPresent "$workingDirAbsolute" "$remote"
fi
}
function logWarningCouldNotWritePullArgs() {
logWarning "was not able to write %s %s into %s\nPlease do it manually or use %s when using 'gt pull' with the remote %s" "$@"
}