#!/bin/sh set -eu cd -- "$(dirname "$0")/../sub/lib" . ./log.sh . ./flag.sh . ./release.sh cd - >/dev/null help() { arg0="$0" if [ "$0" = sh ]; then arg0="curl -fsSL https://d2lang.com/install.sh | sh -s --" fi cat < but the release archive in ~/.cache/d2/release will remain. --uninstall: Uninstall the installed version of d2. The --method and --prefix flags must be the same as for installation. i.e if you used --method standalone you must again use --method standalone for uninstallation. With detect, the install script will try to use the OS package manager to uninstall instead. note: tala will also be uninstalled if installed. -x, --trace: Run script with set -x. All downloaded archives are cached into ~/.cache/d2/release. use \$XDG_CACHE_HOME to change path of the cached assets. Release archives are unarchived into \$PREFIX/lib/d2/d2- note: Deleting the unarchived releases will cause --uninstall to stop working. You can rerun install.sh to update your version of D2. install.sh will avoid reinstalling if the installed version is the latest unless --force is passed. See https://github.com/terrastruct/d2/blob/master/docs/INSTALL.md#security for documentation on its security. EOF } main() { while flag_parse "$@"; do case "$FLAG" in h|help) help return 0 ;; d|dry-run) flag_noarg && shift "$FLAGSHIFT" DRY_RUN=1 ;; version) flag_nonemptyarg && shift "$FLAGSHIFT" VERSION=$FLAGARG ;; tala) shift "$FLAGSHIFT" TALA=${FLAGARG:-latest} ;; edge) flag_noarg && shift "$FLAGSHIFT" EDGE=1 echoerr "$FLAGRAW is currently unimplemented" return 1 ;; method) flag_nonemptyarg && shift "$FLAGSHIFT" METHOD=$FLAGARG ;; prefix) flag_nonemptyarg && shift "$FLAGSHIFT" export PREFIX=$FLAGARG ;; force) flag_noarg && shift "$FLAGSHIFT" FORCE=1 ;; uninstall) flag_noarg && shift "$FLAGSHIFT" UNINSTALL=1 ;; x|trace) flag_noarg && shift "$FLAGSHIFT" set -x export TRACE=1 ;; *) flag_errusage "unrecognized flag $FLAGRAW" ;; esac done shift "$FLAGSHIFT" if [ $# -gt 0 ]; then flag_errusage "no arguments are accepted" fi REPO=${REPO:-terrastruct/d2} ensure_os ensure_arch ensure_prefix CACHE_DIR=$(cache_dir) mkdir -p "$CACHE_DIR" METHOD=${METHOD:-detect} INSTALL_DIR=$PREFIX/lib/d2 case $METHOD in detect) case "$OS" in macos) if command -v brew >/dev/null; then log "detected macOS with homebrew, using homebrew for installation" METHOD=homebrew else warn "detected macOS without homebrew, falling back to --method=standalone" METHOD=standalone fi ;; linux|windows) METHOD=standalone ;; *) warn "unrecognized OS $OS, falling back to --method=standalone" METHOD=standalone ;; esac ;; standalone) ;; homebrew) ;; *) echoerr "unknown installation method $METHOD" return 1 ;; esac if [ -n "${UNINSTALL-}" ]; then uninstall if [ -n "${DRY_RUN-}" ]; then bigheader "Rerun without --dry-run to execute printed commands and perform install." fi else install if [ -n "${DRY_RUN-}" ]; then bigheader "Rerun without --dry-run to execute printed commands and perform install." fi fi } install() { case $METHOD in standalone) install_d2_standalone if [ -n "${TALA-}" ]; then # Run in subshell to avoid overwriting VERSION. TALA_VERSION="$( RELEASE_INFO= install_tala_standalone && echo "$VERSION" )" fi ;; homebrew) install_d2_brew if [ -n "${TALA-}" ]; then install_tala_brew; fi ;; esac FGCOLOR=2 bigheader 'next steps' case $METHOD in standalone) install_post_standalone ;; homebrew) install_post_brew ;; esac install_post_warn } install_post_standalone() { log "d2-$VERSION-$OS-$ARCH has been successfully installed into $PREFIX" if [ -n "${TALA-}" ]; then log "tala-$TALA_VERSION-$OS-$ARCH has been successfully installed into $PREFIX" fi log "Rerun this install script with --uninstall to uninstall." log if ! echo "$PATH" | grep -qF "$PREFIX/bin"; then logcat >&2 </dev/null | grep -qF "$PREFIX/share/man"; then logcat >&2 </dev/null; then INSTALLED_VERSION=$(d2 --version) if [ "$INSTALLED_VERSION" != "$VERSION" ]; then warn "newly installed d2 $VERSION is shadowed by d2 $INSTALLED_VERSION in \$PATH" fi fi if [ -n "${TALA-}" ] && command -v d2plugin-tala >/dev/null; then INSTALLED_TALA_VERSION=$(d2plugin-tala --version) if [ "$INSTALLED_TALA_VERSION" != "$TALA_VERSION" ]; then warn "newly installed d2plugin-tala $TALA_VERSION is shadowed by d2plugin-tala $INSTALLED_TALA_VERSION in \$PATH" fi fi } install_d2_standalone() { VERSION=${VERSION:-latest} header "installing d2-$VERSION" if [ "$VERSION" = latest ]; then fetch_release_info fi if command -v d2 >/dev/null; then INSTALLED_VERSION="$(d2 --version)" if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then log "skipping installation as d2 $VERSION is already installed." return 0 fi log "uninstalling d2 $INSTALLED_VERSION to install $VERSION" if ! uninstall_d2_standalone; then warn "failed to uninstall d2 $INSTALLED_VERSION" fi fi ARCHIVE="d2-$VERSION-$OS-$ARCH.tar.gz" log "installing standalone release $ARCHIVE from github" fetch_release_info asset_line=$(sh_c 'cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1') asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"') fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream' ensure_prefix_sh_c "$sh_c" mkdir -p "'$INSTALL_DIR'" "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'" } install_d2_brew() { header "installing d2 with homebrew" sh_c brew update sh_c brew install d2 } install_tala_standalone() { REPO="${REPO_TALA:-terrastruct/tala}" VERSION=$TALA header "installing tala-$VERSION" if [ "$VERSION" = latest ]; then fetch_release_info fi if command -v d2plugin-tala >/dev/null; then INSTALLED_VERSION="$(d2plugin-tala --version)" if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then log "skipping installation as tala $VERSION is already installed." return 0 fi log "uninstalling tala $INSTALLED_VERSION to install $VERSION" if ! uninstall_tala_standalone; then warn "failed to uninstall tala $INSTALLED_VERSION" fi fi ARCHIVE="tala-$VERSION-$OS-$ARCH.tar.gz" log "installing standalone release $ARCHIVE from github" fetch_release_info asset_line=$(sh_c 'cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1') asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"') fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream' ensure_prefix_sh_c "$sh_c" mkdir -p "'$INSTALL_DIR'" "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'" } install_tala_brew() { header "installing tala with homebrew" sh_c brew update sh_c brew install terrastruct/tap/tala } uninstall() { # We uninstall tala first as package managers require that it be uninstalled before # uninstalling d2 as TALA depends on d2. if command -v d2plugin-tala >/dev/null; then INSTALLED_VERSION="$(d2plugin-tala --version)" header "uninstalling tala-$INSTALLED_VERSION" case $METHOD in standalone) uninstall_tala_standalone ;; homebrew) uninstall_tala_brew ;; esac elif [ "${TALA-}" ]; then warn "no version of tala installed" fi if ! command -v d2 >/dev/null; then warn "no version of d2 installed" return 0 fi INSTALLED_VERSION="$(d2 --version)" header "uninstalling d2-$INSTALLED_VERSION" case $METHOD in standalone) uninstall_d2_standalone ;; homebrew) uninstall_d2_brew ;; esac } uninstall_d2_standalone() { log "uninstalling standalone release of d2-$INSTALLED_VERSION" if [ ! -e "$INSTALL_DIR/d2-$INSTALLED_VERSION" ]; then warn "missing standalone install release directory $INSTALL_DIR/d2-$INSTALLED_VERSION" warn "d2 must have been installed via some other installation method." return 1 fi ensure_prefix_sh_c "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'" "$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION" } uninstall_d2_brew() { sh_c brew remove d2 } uninstall_tala_standalone() { log "uninstalling standalone release tala-$INSTALLED_VERSION" if [ ! -e "$INSTALL_DIR/tala-$INSTALLED_VERSION" ]; then warn "missing standalone install release directory $INSTALL_DIR/tala-$INSTALLED_VERSION" warn "tala must have been installed via some other installation method." return 1 fi ensure_prefix_sh_c "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'" "$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION" } uninstall_tala_brew() { sh_c brew remove tala } cache_dir() { if [ -n "${XDG_CACHE_HOME-}" ]; then echo "$XDG_CACHE_HOME/d2/release" elif [ -n "${HOME-}" ]; then echo "$HOME/.cache/d2/release" else echo "/tmp/d2-cache/release" fi } fetch_release_info() { if [ -n "${RELEASE_INFO-}" ]; then return 0 fi log "fetching info on $VERSION version of $REPO" RELEASE_INFO=$(mktempd)/release-info.json if [ "$VERSION" = latest ]; then release_info_url="https://api.github.com/repos/$REPO/releases/$VERSION" else release_info_url="https://api.github.com/repos/$REPO/releases/tags/$VERSION" fi DRY_RUN= fetch_gh "$release_info_url" "$RELEASE_INFO" \ 'application/json' VERSION=$(cat "$RELEASE_INFO" | grep -m1 tag_name | sed 's/^.*: "\(.*\)",$/\1/g') } curl_gh() { sh_c curl -fL ${GITHUB_TOKEN:+"-H \"Authorization: Bearer \$GITHUB_TOKEN\""} "$@" } fetch_gh() { url=$1 file=$2 accept=$3 if [ -e "$file" ]; then log "reusing $file" return fi curl_gh -#o "$file.inprogress" -C- -H "'Accept: $accept'" "$url" sh_c mv "$file.inprogress" "$file" } # The main function does more than provide organization. It provides robustness in that if # the install script was to only partial download into sh, sh will not execute it because # main is not invoked until the very last byte. main "$@"