...
1#!/bin/sh
2set -eu
3
4cd -- "$(dirname "$0")/../sub/lib"
5. ./log.sh
6. ./flag.sh
7. ./release.sh
8cd - >/dev/null
9
10help() {
11 arg0="$0"
12 if [ "$0" = sh ]; then
13 arg0="curl -fsSL https://d2lang.com/install.sh | sh -s --"
14 fi
15
16 cat <<EOF
17usage: $arg0 [-d|--dry-run] [--version vX.X.X] [--edge] [--method detect] [--prefix path]
18 [--tala latest] [--force] [--uninstall] [-x|--trace]
19
20install.sh automates the installation of D2 onto your system. It currently only supports
21the installation of standalone releases from GitHub and via Homebrew on macOS. See the
22docs for --detect below for more information
23
24If you pass --edge, it will clone the source, build a release and install from it.
25--edge is incompatible with --tala and currently unimplemented.
26
27\$PREFIX in the docs below refers to the path set by --prefix. See docs on the --prefix
28flag below for the default.
29
30Flags:
31
32-d, --dry-run
33 Pass to have install.sh show the install method and flags that will be used to install
34 without executing them. Very useful to understand what changes it will make to your system.
35
36--version vX.X.X
37 Pass to have install.sh install the given version instead of the latest version.
38 warn: The version may not be obeyed with package manager installations. Use
39 --method=standalone to enforce the version.
40
41--edge
42 Pass to build and install D2 from source. This will still use --method if set to detect
43 to install the release archive for your OS, whether it's apt, yum, brew or standalone
44 if an unsupported package manager is used.
45
46 To install from source like a dev would, use go install oss.terrastruct.com/d2. There's
47 also ./ci/release/build.sh --install to build and install a proper standalone release
48 including manpages. The proper release will also ensure d2 --version shows the correct
49 version by embedding the commit hash into the binary.
50
51 note: currently unimplemented.
52 warn: incompatible with --tala as TALA is closed source.
53
54--method [detect | standalone | homebrew ]
55 Pass to control the method by which to install. Right now we only support standalone
56 releases from GitHub but later we'll add support for brew, rpm, deb and more.
57
58 - detect will use your OS's package manager automatically.
59 So far it only detects macOS and automatically uses homebrew.
60 - homebrew uses https://brew.sh/ which is a macOS and Linux package manager.
61 - standalone installs a standalone release archive into the unix hierarchy path
62 specified by --prefix
63
64--prefix path
65 Controls the unix hierarchy path into which standalone releases are installed.
66 Defaults to /usr/local or ~/.local if /usr/local is not writable by the current user.
67 Remember that whatever you use, you must have the bin directory of your prefix path in
68 \$PATH to execute the d2 binary. For example, if my prefix directory is /usr/local then
69 my \$PATH must contain /usr/local/bin.
70 You may also need to include \$PREFIX/share/man into \$MANPATH.
71 install.sh will tell you whether \$PATH or \$MANPATH need to be updated after successful
72 installation.
73
74--tala [latest]
75 Install Terrastruct's closed source TALA for improved layouts.
76 See https://github.com/terrastruct/tala
77 It optionally takes an argument of the TALA version to install.
78 Installation obeys all other flags, just like the installation of d2. For example,
79 the d2plugin-tala binary will be installed into \$PREFIX/bin/d2plugin-tala
80 warn: The version may not be obeyed with package manager installations. Use
81 --method=standalone to enforce the version.
82
83--force:
84 Force installation over the existing version even if they match. It will attempt a
85 uninstall first before installing the new version. The installed release tree
86 will be deleted from \$PREFIX/lib/d2/d2-<VERSION> but the release archive in
87 ~/.cache/d2/release will remain.
88
89--uninstall:
90 Uninstall the installed version of d2. The --method and --prefix flags must be the same
91 as for installation. i.e if you used --method standalone you must again use --method
92 standalone for uninstallation. With detect, the install script will try to use the OS
93 package manager to uninstall instead.
94 note: tala will also be uninstalled if installed.
95
96-x, --trace:
97 Run script with set -x.
98
99All downloaded archives are cached into ~/.cache/d2/release. use \$XDG_CACHE_HOME to change
100path of the cached assets. Release archives are unarchived into \$PREFIX/lib/d2/d2-<VERSION>
101
102note: Deleting the unarchived releases will cause --uninstall to stop working.
103
104You can rerun install.sh to update your version of D2. install.sh will avoid reinstalling
105if the installed version is the latest unless --force is passed.
106
107See https://github.com/terrastruct/d2/blob/master/docs/INSTALL.md#security for
108documentation on its security.
109EOF
110}
111
112main() {
113 while flag_parse "$@"; do
114 case "$FLAG" in
115 h|help)
116 help
117 return 0
118 ;;
119 d|dry-run)
120 flag_noarg && shift "$FLAGSHIFT"
121 DRY_RUN=1
122 ;;
123 version)
124 flag_nonemptyarg && shift "$FLAGSHIFT"
125 VERSION=$FLAGARG
126 ;;
127 tala)
128 shift "$FLAGSHIFT"
129 TALA=${FLAGARG:-latest}
130 ;;
131 edge)
132 flag_noarg && shift "$FLAGSHIFT"
133 EDGE=1
134 echoerr "$FLAGRAW is currently unimplemented"
135 return 1
136 ;;
137 method)
138 flag_nonemptyarg && shift "$FLAGSHIFT"
139 METHOD=$FLAGARG
140 ;;
141 prefix)
142 flag_nonemptyarg && shift "$FLAGSHIFT"
143 export PREFIX=$FLAGARG
144 ;;
145 force)
146 flag_noarg && shift "$FLAGSHIFT"
147 FORCE=1
148 ;;
149 uninstall)
150 flag_noarg && shift "$FLAGSHIFT"
151 UNINSTALL=1
152 ;;
153 x|trace)
154 flag_noarg && shift "$FLAGSHIFT"
155 set -x
156 export TRACE=1
157 ;;
158 *)
159 flag_errusage "unrecognized flag $FLAGRAW"
160 ;;
161 esac
162 done
163 shift "$FLAGSHIFT"
164
165 if [ $# -gt 0 ]; then
166 flag_errusage "no arguments are accepted"
167 fi
168
169 REPO=${REPO:-terrastruct/d2}
170 ensure_os
171 ensure_arch
172 ensure_prefix
173 CACHE_DIR=$(cache_dir)
174 mkdir -p "$CACHE_DIR"
175 METHOD=${METHOD:-detect}
176 INSTALL_DIR=$PREFIX/lib/d2
177
178 case $METHOD in
179 detect)
180 case "$OS" in
181 macos)
182 if command -v brew >/dev/null; then
183 log "detected macOS with homebrew, using homebrew for installation"
184 METHOD=homebrew
185 else
186 warn "detected macOS without homebrew, falling back to --method=standalone"
187 METHOD=standalone
188 fi
189 ;;
190 linux|windows)
191 METHOD=standalone
192 ;;
193 *)
194 warn "unrecognized OS $OS, falling back to --method=standalone"
195 METHOD=standalone
196 ;;
197 esac
198 ;;
199 standalone) ;;
200 homebrew) ;;
201 *)
202 echoerr "unknown installation method $METHOD"
203 return 1
204 ;;
205 esac
206
207 if [ -n "${UNINSTALL-}" ]; then
208 uninstall
209 if [ -n "${DRY_RUN-}" ]; then
210 bigheader "Rerun without --dry-run to execute printed commands and perform install."
211 fi
212 else
213 install
214 if [ -n "${DRY_RUN-}" ]; then
215 bigheader "Rerun without --dry-run to execute printed commands and perform install."
216 fi
217 fi
218}
219
220install() {
221 case $METHOD in
222 standalone)
223 install_d2_standalone
224 if [ -n "${TALA-}" ]; then
225 # Run in subshell to avoid overwriting VERSION.
226 TALA_VERSION="$( RELEASE_INFO= install_tala_standalone && echo "$VERSION" )"
227 fi
228 ;;
229 homebrew)
230 install_d2_brew
231 if [ -n "${TALA-}" ]; then install_tala_brew; fi
232 ;;
233 esac
234
235 FGCOLOR=2 bigheader 'next steps'
236 case $METHOD in
237 standalone) install_post_standalone ;;
238 homebrew) install_post_brew ;;
239 esac
240 install_post_warn
241}
242
243install_post_standalone() {
244 log "d2-$VERSION-$OS-$ARCH has been successfully installed into $PREFIX"
245 if [ -n "${TALA-}" ]; then
246 log "tala-$TALA_VERSION-$OS-$ARCH has been successfully installed into $PREFIX"
247 fi
248 log "Rerun this install script with --uninstall to uninstall."
249 log
250 if ! echo "$PATH" | grep -qF "$PREFIX/bin"; then
251 logcat >&2 <<EOF
252Extend your \$PATH to use d2:
253 export PATH=$PREFIX/bin:\$PATH
254Then run:
255 ${TALA:+D2_LAYOUT=tala }d2 --help
256EOF
257 else
258 log "Run ${TALA:+D2_LAYOUT=tala }d2 --help for usage."
259 fi
260 if ! manpath 2>/dev/null | grep -qF "$PREFIX/share/man"; then
261 logcat >&2 <<EOF
262Extend your \$MANPATH to view d2's manpages:
263 export MANPATH=$PREFIX/share/man:\$MANPATH
264Then run:
265 man d2
266EOF
267 if [ -n "${TALA-}" ]; then
268 log " man d2plugin-tala"
269 fi
270 else
271 log "Run man d2 for detailed docs."
272 if [ -n "${TALA-}" ]; then
273 log "Run man d2plugin-tala for detailed TALA docs."
274 fi
275 fi
276}
277
278install_post_brew() {
279 log "d2 has been successfully installed with homebrew."
280 if [ -n "${TALA-}" ]; then
281 log "tala has been successfully installed with homebrew."
282 fi
283 log "Rerun this install script with --uninstall to uninstall."
284 log
285 log "Run ${TALA:+D2_LAYOUT=tala }d2 --help for usage."
286 log "Run man d2 for detailed docs."
287 if [ -n "${TALA-}" ]; then
288 log "Run man d2plugin-tala for detailed TALA docs."
289 fi
290
291 VERSION=$(brew info d2 | head -n1 | cut -d' ' -f4)
292 VERSION=${VERSION%,}
293 if [ -n "${TALA-}" ]; then
294 TALA_VERSION=$(brew info tala | head -n1 | cut -d' ' -f4)
295 TALA_VERSION=${TALA_VERSION%,}
296 fi
297}
298
299install_post_warn() {
300 if command -v d2 >/dev/null; then
301 INSTALLED_VERSION=$(d2 --version)
302 if [ "$INSTALLED_VERSION" != "$VERSION" ]; then
303 warn "newly installed d2 $VERSION is shadowed by d2 $INSTALLED_VERSION in \$PATH"
304 fi
305 fi
306 if [ -n "${TALA-}" ] && command -v d2plugin-tala >/dev/null; then
307 INSTALLED_TALA_VERSION=$(d2plugin-tala --version)
308 if [ "$INSTALLED_TALA_VERSION" != "$TALA_VERSION" ]; then
309 warn "newly installed d2plugin-tala $TALA_VERSION is shadowed by d2plugin-tala $INSTALLED_TALA_VERSION in \$PATH"
310 fi
311 fi
312}
313
314install_d2_standalone() {
315 VERSION=${VERSION:-latest}
316 header "installing d2-$VERSION"
317
318 if [ "$VERSION" = latest ]; then
319 fetch_release_info
320 fi
321
322 if command -v d2 >/dev/null; then
323 INSTALLED_VERSION="$(d2 --version)"
324 if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then
325 log "skipping installation as d2 $VERSION is already installed."
326 return 0
327 fi
328 log "uninstalling d2 $INSTALLED_VERSION to install $VERSION"
329 if ! uninstall_d2_standalone; then
330 warn "failed to uninstall d2 $INSTALLED_VERSION"
331 fi
332 fi
333
334 ARCHIVE="d2-$VERSION-$OS-$ARCH.tar.gz"
335 log "installing standalone release $ARCHIVE from github"
336
337 fetch_release_info
338 asset_line=$(sh_c 'cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1')
339 asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"')
340 fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream'
341
342 ensure_prefix_sh_c
343 "$sh_c" mkdir -p "'$INSTALL_DIR'"
344 "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE"
345 "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
346}
347
348install_d2_brew() {
349 header "installing d2 with homebrew"
350 sh_c brew update
351 sh_c brew install d2
352}
353
354install_tala_standalone() {
355 REPO="${REPO_TALA:-terrastruct/tala}"
356 VERSION=$TALA
357
358 header "installing tala-$VERSION"
359
360 if [ "$VERSION" = latest ]; then
361 fetch_release_info
362 fi
363
364 if command -v d2plugin-tala >/dev/null; then
365 INSTALLED_VERSION="$(d2plugin-tala --version)"
366 if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then
367 log "skipping installation as tala $VERSION is already installed."
368 return 0
369 fi
370 log "uninstalling tala $INSTALLED_VERSION to install $VERSION"
371 if ! uninstall_tala_standalone; then
372 warn "failed to uninstall tala $INSTALLED_VERSION"
373 fi
374 fi
375
376 ARCHIVE="tala-$VERSION-$OS-$ARCH.tar.gz"
377 log "installing standalone release $ARCHIVE from github"
378
379 fetch_release_info
380 asset_line=$(sh_c 'cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1')
381 asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"')
382
383 fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream'
384
385 ensure_prefix_sh_c
386 "$sh_c" mkdir -p "'$INSTALL_DIR'"
387 "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE"
388 "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
389}
390
391install_tala_brew() {
392 header "installing tala with homebrew"
393 sh_c brew update
394 sh_c brew install terrastruct/tap/tala
395}
396
397uninstall() {
398 # We uninstall tala first as package managers require that it be uninstalled before
399 # uninstalling d2 as TALA depends on d2.
400 if command -v d2plugin-tala >/dev/null; then
401 INSTALLED_VERSION="$(d2plugin-tala --version)"
402 header "uninstalling tala-$INSTALLED_VERSION"
403 case $METHOD in
404 standalone) uninstall_tala_standalone ;;
405 homebrew) uninstall_tala_brew ;;
406 esac
407 elif [ "${TALA-}" ]; then
408 warn "no version of tala installed"
409 fi
410
411 if ! command -v d2 >/dev/null; then
412 warn "no version of d2 installed"
413 return 0
414 fi
415
416 INSTALLED_VERSION="$(d2 --version)"
417 header "uninstalling d2-$INSTALLED_VERSION"
418 case $METHOD in
419 standalone) uninstall_d2_standalone ;;
420 homebrew) uninstall_d2_brew ;;
421 esac
422}
423
424uninstall_d2_standalone() {
425 log "uninstalling standalone release of d2-$INSTALLED_VERSION"
426
427 if [ ! -e "$INSTALL_DIR/d2-$INSTALLED_VERSION" ]; then
428 warn "missing standalone install release directory $INSTALL_DIR/d2-$INSTALLED_VERSION"
429 warn "d2 must have been installed via some other installation method."
430 return 1
431 fi
432
433 ensure_prefix_sh_c
434 "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'"
435 "$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION"
436}
437
438uninstall_d2_brew() {
439 sh_c brew remove d2
440}
441
442uninstall_tala_standalone() {
443 log "uninstalling standalone release tala-$INSTALLED_VERSION"
444
445 if [ ! -e "$INSTALL_DIR/tala-$INSTALLED_VERSION" ]; then
446 warn "missing standalone install release directory $INSTALL_DIR/tala-$INSTALLED_VERSION"
447 warn "tala must have been installed via some other installation method."
448 return 1
449 fi
450
451 ensure_prefix_sh_c
452 "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'"
453 "$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION"
454}
455
456uninstall_tala_brew() {
457 sh_c brew remove tala
458}
459
460cache_dir() {
461 if [ -n "${XDG_CACHE_HOME-}" ]; then
462 echo "$XDG_CACHE_HOME/d2/release"
463 elif [ -n "${HOME-}" ]; then
464 echo "$HOME/.cache/d2/release"
465 else
466 echo "/tmp/d2-cache/release"
467 fi
468}
469
470fetch_release_info() {
471 if [ -n "${RELEASE_INFO-}" ]; then
472 return 0
473 fi
474
475 log "fetching info on $VERSION version of $REPO"
476 RELEASE_INFO=$(mktempd)/release-info.json
477 if [ "$VERSION" = latest ]; then
478 release_info_url="https://api.github.com/repos/$REPO/releases/$VERSION"
479 else
480 release_info_url="https://api.github.com/repos/$REPO/releases/tags/$VERSION"
481 fi
482 DRY_RUN= fetch_gh "$release_info_url" "$RELEASE_INFO" \
483 'application/json'
484 VERSION=$(cat "$RELEASE_INFO" | grep -m1 tag_name | sed 's/^.*: "\(.*\)",$/\1/g')
485}
486
487curl_gh() {
488 sh_c curl -fL ${GITHUB_TOKEN:+"-H \"Authorization: Bearer \$GITHUB_TOKEN\""} "$@"
489}
490
491fetch_gh() {
492 url=$1
493 file=$2
494 accept=$3
495
496 if [ -e "$file" ]; then
497 log "reusing $file"
498 return
499 fi
500
501 curl_gh -#o "$file.inprogress" -C- -H "'Accept: $accept'" "$url"
502 sh_c mv "$file.inprogress" "$file"
503}
504
505# The main function does more than provide organization. It provides robustness in that if
506# the install script was to only partial download into sh, sh will not execute it because
507# main is not invoked until the very last byte.
508main "$@"
View as plain text