-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbpkg-install.sh
executable file
·475 lines (403 loc) · 11.1 KB
/
bpkg-install.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
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
#!/bin/bash
# Include config rc file if found
CONFIG_FILE="$HOME/.bpkgrc"
[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE"
## set defaults
if [[ ${#BPKG_REMOTES[@]} -eq 0 ]]; then
BPKG_REMOTES[0]=${BPKG_REMOTE-https://raw.githubusercontent.com}
BPKG_GIT_REMOTES[0]=${BPKG_GIT_REMOTE-https://github.com}
fi
BPKG_USER="${BPKG_USER:-bpkg}"
## check parameter consistency
validate_parameters () {
if [[ ${#BPKG_GIT_REMOTES[@]} -ne ${#BPKG_REMOTES[@]} ]]; then
mesg='BPKG_GIT_REMOTES[%d] differs in size from BPKG_REMOTES[%d] array'
fmesg=$(printf "$mesg" "${#BPKG_GIT_REMOTES[@]}" "${#BPKG_REMOTES[@]}")
error "$fmesg"
return 1
fi
return 0
}
## outut usage
usage () {
echo 'usage: bpkg-install [-h|--help]'
echo ' or: bpkg-install [-g|--global] <package>'
echo ' or: bpkg-install [-g|--global] <user>/<package>'
}
## format and output message
message () {
if type -f bpkg-term > /dev/null 2>&1; then
bpkg-term color "${1}"
fi
shift
printf " ${1}"
shift
if type -f bpkg-term > /dev/null 2>&1; then
bpkg-term reset
fi
printf ': '
if type -f bpkg-term > /dev/null 2>&1; then
bpkg-term reset
bpkg-term bright
fi
printf "%s\n" "${@}"
if type -f bpkg-term > /dev/null 2>&1; then
bpkg-term reset
fi
}
## output error
error () {
{
message 'red' 'error' "${@}"
} >&2
}
## output warning
warn () {
{
message 'yellow' 'warn' "${@}"
} >&2
}
## output info
info () {
local title='info'
if (( "${#}" > 1 )); then
title="${1}"
shift
fi
message 'cyan' "${title}" "${@}"
}
save_remote_file () {
local auth_param path url
url="${1}"
path="${2}"
auth_param="${3:-}"
local dirname="$(dirname "${path}")"
# Make sure directory exists
if [[ ! -d "${dirname}" ]];then
mkdir -p "${dirname}"
fi
if [[ "${auth_param}" ]];then
curl --silent -L -o "${path}" -u "${auth_param}" "${url}"
else
curl --silent -L -o "${path}" "${url}"
fi
}
url_exists () {
local auth_param exists url
url="${1}"
auth_param="${2:-}"
exists=0
if [[ "${auth_param}" ]];then
status=$(curl --silent -L -w '%{http_code}' -o '/dev/null' -u "${auth_param}" "${url}")
result="$?"
else
status=$(curl --silent -L -w '%{http_code}' -o '/dev/null' "${url}")
result="$?"
fi
# In some rare cases, curl will return CURLE_WRITE_ERROR (23) when writing
# to `/dev/null`. In such a case we do not care that such an error occured.
# We are only interested in the status, which *will* be available regardless.
if [[ '0' != "${result}" && '23' != "${result}" ]] || (( status >= 400 )); then
exists=1
fi
return "${exists}"
}
read_package_json () {
local auth_param url
url="${1}"
auth_param="${2:-}"
if [[ "${auth_param}" ]];then
curl --silent -L -u "${auth_param}" "${url}"
else
curl --silent -L "${url}"
fi
}
## Install a bash package
bpkg_install () {
local pkg=''
local let needs_global=1
for opt in "${@}"; do
if [[ '-' = "${opt:0:1}" ]]; then
continue
fi
pkg="${opt}"
break
done
for opt in "${@}"; do
case "${opt}" in
-h|--help)
usage
return 0
;;
-g|--global)
shift
needs_global=1
;;
*)
if [[ '-' = "${opt:0:1}" ]]; then
echo 2>&1 "error: Unknown argument \`${1}'"
usage
return 1
fi
;;
esac
done
## ensure there is a package to install
if [[ -z "${pkg}" ]]; then
usage
return 1
fi
echo
## Check each remote in order
local let i=0
for remote in "${BPKG_REMOTES[@]}"; do
local git_remote=${BPKG_GIT_REMOTES[$i]}
bpkg_install_from_remote "$pkg" "$remote" "$git_remote" $needs_global
if [[ "$?" == '0' ]]; then
return 0
elif [[ "$?" == '2' ]]; then
error 'fatal error occurred during install'
return 1
fi
i=$((i+1))
done
error 'package not found on any remote'
return 1
}
## try to install a package from a specific remote
## returns values:
## 0: success
## 1: the package was not found on the remote
## 2: a fatal error occurred
bpkg_install_from_remote () {
local pkg=$1
local remote=$2
local git_remote=$3
local let needs_global=$4
local cwd=$(pwd)
local url=''
local uri=''
local version=''
local status=''
local json=''
local user=''
local name=''
local version=''
local auth_param=''
local let has_pkg_json=1
declare -a local pkg_parts=()
declare -a local remote_parts=()
declare -a local scripts=()
declare -a local files=()
## get version if available
{
OLDIFS="${IFS}"
IFS="@"
pkg_parts=(${pkg})
IFS="${OLDIFS}"
}
if [[ ${#pkg_parts[@]} -eq 1 ]]; then
version='main'
#info "Using latest (main)"
elif [[ ${#pkg_parts[@]} -eq 2 ]]; then
name="${pkg_parts[0]}"
version="${pkg_parts[1]}"
else
error 'Error parsing package version'
return 1
fi
## split by user name and repo
{
OLDIFS="${IFS}"
IFS='/'
pkg_parts=(${pkg})
IFS="${OLDIFS}"
}
if [[ ${#pkg_parts[@]} -eq 1 ]]; then
user="${BPKG_USER}"
name="${pkg_parts[0]}"
elif [[ ${#pkg_parts[@]} -eq 2 ]]; then
user="${pkg_parts[0]}"
name="${pkg_parts[1]}"
else
error 'Unable to determine package name'
return 1
fi
## clean up name of weird trailing
## versions and slashes
name=${name/@*//}
name=${name////}
## check to see if remote is raw with oauth (GHE)
if [[ "${remote:0:10}" == "raw-oauth|" ]]; then
info 'Using OAUTH basic with content requests'
OLDIFS="${IFS}"
IFS="'|'"
local remote_parts=($remote)
IFS="${OLDIFS}"
local token=${remote_parts[1]}
remote=${remote_parts[2]}
auth_param="$token:x-oauth-basic"
uri="/${user}/${name}/raw/${version}"
## If git remote is a URL, and doesn't contain token information, we
## inject it into the <user>@host field
if [[ "$git_remote" == https://* ]] && [[ "$git_remote" != *x-oauth-basic* ]] && [[ "$git_remote" != *${token}* ]]; then
git_remote=${git_remote/https:\/\//https:\/\/$token:x-oauth-basic@}
fi
else
uri="/${user}/${name}/${version}"
fi
## clean up extra slashes in uri
uri=${uri/\/\///}
info "Install $uri from remote $remote [$git_remote]"
## Ensure remote is reachable
## If a remote is totally down, this will be considered a fatal
## error since the user may have intended to install the package
## from the broken remote.
{
if ! url_exists "${remote}" "${auth_param}"; then
error "Remote unreachable: ${remote}"
return 2
fi
}
## build url
url="${remote}${uri}"
repo_url="${git_remote}/${user}/${name}.git"
## determine if 'package.json' exists at url
{
if ! url_exists "${url}/package.json?$(date +%s)" "${auth_param}"; then
warn 'package.json doesn`t exist'
has_pkg_json=0
# check to see if there's a Makefile. If not, this is not a valid package
if ! url_exists "${url}/Makefile?$(date +%s)" "${auth_param}"; then
warn "Makefile not found, skipping remote: $url"
return 1
fi
fi
}
## read package.json
json=$(read_package_json "${url}/package.json?$(date +%s)" "${auth_param}")
if (( 1 == has_pkg_json )); then
## get package name from 'package.json'
name="$(
echo -n "${json}" |
bpkg-json -b |
grep -m 1 '"name"' |
awk '{ $1=""; print $0 }' |
tr -d '\"' |
tr -d ' '
)"
## check if forced global
if [[ "$(echo -n "${json}" | bpkg-json -b | grep '\["global"\]' | awk '{ print $2 }' | tr -d '"')" == 'true' ]]; then
needs_global=1
fi
## construct scripts array
{
scripts=$(echo -n "${json}" | bpkg-json -b | grep '\["scripts' | awk '{ print $2 }' | tr -d '"')
## create array by splitting on newline
OLDIFS="${IFS}"
IFS=$'\n'
scripts=(${scripts[@]})
IFS="${OLDIFS}"
}
## construct files array
{
files=$(echo -n "${json}" | bpkg-json -b | grep '\["files' | awk '{ print $2 }' | tr -d '"')
## create array by splitting on newline
OLDIFS="${IFS}"
IFS=$'\n'
files=(${files[@]})
IFS="${OLDIFS}"
}
fi
## build global if needed
if (( 1 == needs_global )); then
if (( 1 == has_pkg_json )); then
## install bin if needed
build="$(echo -n "${json}" | bpkg-json -b | grep '\["install"\]' | awk '{$1=""; print $0 }' | tr -d '\"')"
build="$(echo -n "${build}" | sed -e 's/^ *//' -e 's/ *$//')"
fi
if [[ -z "${build}" ]]; then
warn 'Missing build script'
warn 'Trying `make install`...'
build='make install'
fi
if [ -z "$PREFIX" ]; then
if [ "$USER" == "root" ]; then
PREFIX="/usr/local"
else
PREFIX="$HOME/.local"
fi
build="env PREFIX=$PREFIX $build"
fi
{ (
## go to tmp dir
cd "$( [[ ! -z "${TMPDIR}" ]] && echo "${TMPDIR}" || echo /tmp)" &&
## prune existing
rm -rf "${name}-${version}" &&
## shallow clone
info "Cloning ${repo_url} to ${name}-${version}"
git clone "${repo_url}" "${name}-${version}" &&
(
## move into directory
cd "${name}-${version}" &&
git checkout ${version} &&
## build
info "Performing install: \`${build}'"
build_output=$(eval "${build}")
echo "${build_output}"
) &&
## clean up
rm -rf "${name}-${version}"
) }
## perform local install otherwise
else
## copy package.json over
save_remote_file "${url}/package.json" "${cwd}/deps/${name}/package.json" "${auth_param}"
## make 'deps/' directory if possible
mkdir -p "${cwd}/deps/${name}"
## make 'deps/bin' directory if possible
mkdir -p "${cwd}/deps/bin"
# install package dependencies
info "Install dependencies for ${name}"
(cd "${cwd}/deps/${name}" && bpkg getdeps)
## grab each script and place in deps directory
for script in "${scripts[@]}"; do
(
if [[ "${script}" ]];then
local scriptname="$(echo "${script}" | xargs basename )"
info "fetch" "${url}/${script}"
info "write" "${cwd}/deps/${name}/${script}"
save_remote_file "${url}/${script}" "${cwd}/deps/${name}/${script}" "${auth_param}"
scriptname="${scriptname%.*}"
info "${scriptname} to PATH" "${cwd}/deps/bin/${scriptname}"
ln -si "${cwd}/deps/${name}/${script}" "${cwd}/deps/bin/${scriptname}"
chmod u+x "${cwd}/deps/bin/${scriptname}"
fi
)
done
if [[ "${#files[@]}" -gt '0' ]]; then
## grab each file and place in correct directory
for file in "${files[@]}"; do
(
if [[ "${file}" ]];then
local filename="$(echo "${file}" | xargs basename )"
info "fetch" "${url}/${file}"
info "write" "${cwd}/deps/${name}/${file}"
save_remote_file "${url}/${file}" "${cwd}/deps/${name}/${file}" "${auth_param}"
fi
)
done
fi
fi
return 0
}
## Use as lib or perform install
if [[ $0 != $0 ]]; then
export -f bpkg_install
elif validate_parameters; then
bpkg_install "${@}"
exit $?
else
#param validation failed
exit $?
fi