...

Text file src/oss.terrastruct.com/d2/ci/release/_install.sh

Documentation: oss.terrastruct.com/d2/ci/release

     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