...
1#!/bin/bash
2
3set -euo pipefail
4
5# Version and checksums for godel. Values are populated by the godel "dist" task.
6VERSION=2.40.0
7DARWIN_AMD64_CHECKSUM=c0ddf9c67b8bbad9bb362687f79437ca1bf9e9076bc36b212d1be2b659bf23bd
8DARWIN_ARM64_CHECKSUM=2de3ff5e61ebba985e2ab80fbc4ca0add0db34cac5fa4b7776c1b50d1e53463b
9LINUX_AMD64_CHECKSUM=d489cd497218e68a5bd26ad6efa73ff6775e94b653b232585061ad07efdbd684
10LINUX_ARM64_CHECKSUM=c1e03926e7f032e895b25e320f490c3ea696e505b44b27cb5b9014bcd24c4d16
11
12# Downloads file at URL to destination path using wget or curl. Prints an error and exits if wget or curl is not present.
13function download {
14 local url=$1
15 local dst=$2
16
17 # determine whether wget, curl or both are present
18 set +e
19 command -v wget >/dev/null 2>&1
20 local wget_exists=$?
21 command -v curl >/dev/null 2>&1
22 local curl_exists=$?
23 set -e
24
25 # if one of wget or curl is not present, exit with error
26 if [ "$wget_exists" -ne 0 -a "$curl_exists" -ne 0 ]; then
27 echo "wget or curl must be present to download distribution. Install one of these programs and try again or install the distribution manually."
28 exit 1
29 fi
30
31 if [ "$wget_exists" -eq 0 ]; then
32 # attempt download using wget
33 echo "Downloading $url to $dst..."
34 local progress_opt=""
35 if wget --help | grep -q '\--show-progress'; then
36 progress_opt="-q --show-progress"
37 fi
38 set +e
39 wget -O "$dst" $progress_opt "$url"
40 rv=$?
41 set -e
42 if [ "$rv" -eq 0 ]; then
43 # success
44 return
45 fi
46
47 echo "Download failed using command: wget -O $dst $progress_opt $url"
48
49 # curl does not exist, so nothing more to try: exit
50 if [ "$curl_exists" -ne 0 ]; then
51 echo "Download failed using wget and curl was not found. Verify that the distribution URL is correct and try again or install the distribution manually."
52 exit 1
53 fi
54 # curl exists, notify that download will be attempted using curl
55 echo "Attempting download using curl..."
56 fi
57
58 # attempt download using curl
59 echo "Downloading $url to $dst..."
60 set +e
61 curl -f -L -o "$dst" "$url"
62 rv=$?
63 set -e
64 if [ "$rv" -ne 0 ]; then
65 echo "Download failed using command: curl -f -L -o $dst $url"
66 if [ "$wget_exists" -eq 0 ]; then
67 echo "Download failed using wget and curl. Verify that the distribution URL is correct and try again or install the distribution manually."
68 else
69 echo "Download failed using curl and wget was not found. Verify that the distribution URL is correct and try again or install the distribution manually."
70 fi
71 exit 1
72 fi
73}
74
75# verifies that the provided checksum matches the computed SHA-256 checksum of the specified file. If not, echoes an
76# error and exits.
77function verify_checksum {
78 local file=$1
79 local expected_checksum=$2
80 local computed_checksum=$(compute_sha256 $file)
81 if [ "$expected_checksum" != "$computed_checksum" ]; then
82 echo "SHA-256 checksum for $file did not match expected value."
83 echo "Expected: $expected_checksum"
84 echo "Actual: $computed_checksum"
85 exit 1
86 fi
87}
88
89# computes the SHA-256 hash of the provided file. Uses openssl, shasum or sha1sum program.
90function compute_sha256 {
91 local file=$1
92 if command -v openssl >/dev/null 2>&1; then
93 # print SHA-256 hash using openssl
94 openssl dgst -sha256 "$file" | sed -E 's/SHA256\(.*\)= //'
95 elif command -v shasum >/dev/null 2>&1; then
96 # Darwin systems ship with "shasum" utility
97 shasum -a 256 "$file" | sed -E 's/[[:space:]]+.+//'
98 elif command -v sha256sum >/dev/null 2>&1; then
99 # Most Linux systems ship with sha256sum utility
100 sha256sum "$file" | sed -E 's/[[:space:]]+.+//'
101 else
102 echo "Could not find program to calculate SHA-256 checksum for file"
103 exit 1
104 fi
105}
106
107# Verifies that the tgz file at the provided path contains the paths/files that would be expected in a valid gödel
108# distribution with the provided version.
109function verify_dist_tgz_valid {
110 local tgz_path=$1
111 local version=$2
112
113 local expected_paths=("godel-$version/" "godel-$version/bin/darwin-amd64/godel" "godel-$version/bin/darwin-arm64/godel" "godel-$version/bin/linux-amd64/godel" "godel-$version/bin/linux-arm64/godel" "godel-$version/wrapper/godelw" "godel-$version/wrapper/godel/config/")
114 local files=($(tar -tf "$tgz_path"))
115
116 # this is a double-for loop, but fine since $expected_paths is small and bash doesn't have good primitives for set/map/list manipulation
117 for curr_line in "${files[@]}"; do
118 # if all expected paths have been found, terminate
119 if [[ ${#expected_paths[*]} == 0 ]]; then
120 break
121 fi
122
123 # check for expected path and splice out if match is found
124 idx=0
125 for curr_expected in "${expected_paths[@]}"; do
126 if [ "$curr_expected" = "$curr_line" ]; then
127 expected_paths=(${expected_paths[@]:0:idx} ${expected_paths[@]:$(($idx + 1))})
128 break
129 fi
130 idx=$idx+1
131 done
132 done
133
134 # if any expected paths still remain, raise error and exit
135 if [[ ${#expected_paths[*]} > 0 ]]; then
136 echo "Required paths were not present in $tgz_path: ${expected_paths[@]}"
137 exit 1
138 fi
139}
140
141# Verifies that the gödel binary in the distribution reports the expected version when called with the "version"
142# argument. Assumes that a valid gödel distribution directory for the given version exists in the provided directory.
143function verify_godel_version {
144 local base_dir=$1
145 local version=$2
146 local os=$3
147 local arch=$4
148
149 local expected_output="godel version $version"
150 local version_output=$($base_dir/godel-$version/bin/$os-$arch/godel version)
151
152 if [ "$expected_output" != "$version_output" ]; then
153 echo "Version reported by godel executable did not match expected version: expected \"$expected_output\", was \"$version_output\""
154 exit 1
155 fi
156}
157
158# directory of godelw script
159SCRIPT_HOME=$(cd "$(dirname "$0")" && pwd)
160
161# use $GODEL_HOME or default value
162GODEL_BASE_DIR=${GODEL_HOME:-$HOME/.godel}
163
164# determine OS
165OS=""
166EXPECTED_CHECKSUM=""
167case "$(uname)-$(uname -m)" in
168 Darwin-x86_64)
169 OS=darwin
170 ARCH=amd64
171 EXPECTED_CHECKSUM=$DARWIN_AMD64_CHECKSUM
172 ;;
173 Darwin-arm64)
174 OS=darwin
175 ARCH=arm64
176 EXPECTED_CHECKSUM=$DARWIN_ARM64_CHECKSUM
177 ;;
178 Linux-x86_64)
179 OS=linux
180 ARCH=amd64
181 EXPECTED_CHECKSUM=$LINUX_AMD64_CHECKSUM
182 ;;
183 Linux-aarch64)
184 OS=linux
185 ARCH=arm64
186 EXPECTED_CHECKSUM=$LINUX_ARM64_CHECKSUM
187 ;;
188 *)
189 echo "Unsupported operating system-architecture: $(uname)-$(uname -m)"
190 exit 1
191 ;;
192esac
193
194# path to godel binary
195CMD=$GODEL_BASE_DIR/dists/godel-$VERSION/bin/$OS-$ARCH/godel
196
197# godel binary is not present -- download distribution
198if [ ! -f "$CMD" ]; then
199 # get download URL
200 PROPERTIES_FILE=$SCRIPT_HOME/godel/config/godel.properties
201 if [ ! -f "$PROPERTIES_FILE" ]; then
202 echo "Properties file must exist at $PROPERTIES_FILE"
203 exit 1
204 fi
205 DOWNLOAD_URL=$(cat "$PROPERTIES_FILE" | sed -E -n "s/^distributionURL=//p")
206 if [ -z "$DOWNLOAD_URL" ]; then
207 echo "Value for property \"distributionURL\" was empty in $PROPERTIES_FILE"
208 exit 1
209 fi
210 DOWNLOAD_CHECKSUM=$(cat "$PROPERTIES_FILE" | sed -E -n "s/^distributionSHA256=//p")
211
212 # create downloads directory if it does not already exist
213 mkdir -p "$GODEL_BASE_DIR/downloads"
214
215 # download tgz and verify its contents
216 # Download to unique location that includes PID ($$) and use trap ensure that temporary download file is cleaned up
217 # if script is terminated before the file is moved to its destination.
218 DOWNLOAD_DST=$GODEL_BASE_DIR/downloads/godel-$VERSION-$$.tgz
219 download "$DOWNLOAD_URL" "$DOWNLOAD_DST"
220 trap 'rm -rf "$DOWNLOAD_DST"' EXIT
221 if [ -n "$DOWNLOAD_CHECKSUM" ]; then
222 verify_checksum "$DOWNLOAD_DST" "$DOWNLOAD_CHECKSUM"
223 fi
224 verify_dist_tgz_valid "$DOWNLOAD_DST" "$VERSION"
225
226 # create temporary directory for unarchiving, unarchive downloaded file and verify directory
227 TMP_DIST_DIR=$(mktemp -d "$GODEL_BASE_DIR/tmp_XXXXXX" 2>/dev/null || mktemp -d -t "$GODEL_BASE_DIR/tmp_XXXXXX")
228 trap 'rm -rf "$TMP_DIST_DIR"' EXIT
229 tar zxvf "$DOWNLOAD_DST" -C "$TMP_DIST_DIR" >/dev/null 2>&1
230 verify_godel_version "$TMP_DIST_DIR" "$VERSION" "$OS" "$ARCH"
231
232 # rename downloaded file to remove PID portion
233 mv "$DOWNLOAD_DST" "$GODEL_BASE_DIR/downloads/godel-$VERSION.tgz"
234
235 # if destination directory for distribution already exists, remove it
236 if [ -d "$GODEL_BASE_DIR/dists/godel-$VERSION" ]; then
237 rm -rf "$GODEL_BASE_DIR/dists/godel-$VERSION"
238 fi
239
240 # ensure that parent directory of destination exists
241 mkdir -p "$GODEL_BASE_DIR/dists"
242
243 # move expanded distribution directory to destination location. The location of the unarchived directory is known to
244 # be in the same directory tree as the destination, so "mv" should always work.
245 mv "$TMP_DIST_DIR/godel-$VERSION" "$GODEL_BASE_DIR/dists/godel-$VERSION"
246
247 # edge case cleanup: if the destination directory "$GODEL_BASE_DIR/dists/godel-$VERSION" was created prior to the
248 # "mv" operation above, then the move operation will move the source directory into the destination directory. In
249 # this case, remove the directory. It should always be safe to remove this directory because if the directory
250 # existed in the distribution and was non-empty, then the move operation would fail (because non-empty directories
251 # cannot be overwritten by mv). All distributions of a given version are also assumed to be identical. The only
252 # instance in which this would not work is if the distribution purposely contained an empty directory that matched
253 # the name "godel-$VERSION", and this is assumed to never be true.
254 if [ -d "$GODEL_BASE_DIR/dists/godel-$VERSION/godel-$VERSION" ]; then
255 rm -rf "$GODEL_BASE_DIR/dists/godel-$VERSION/godel-$VERSION"
256 fi
257fi
258
259verify_checksum "$CMD" "$EXPECTED_CHECKSUM"
260
261# execute command
262$CMD --wrapper "$SCRIPT_HOME/$(basename "$0")" "$@"
View as plain text