#!/bin/sh set -eu # ************* # DO NOT EDIT # # install.sh was bundled together from # # - ./ci/sub/lib/rand.sh # - ./ci/sub/lib/log.sh # - ./ci/sub/lib/flag.sh # - ./ci/sub/lib/release.sh # - ./ci/release/_install.sh # # The last of which implements the installation logic. # # Generated by ./ci/release/gen_install.sh. # ************* #!/bin/sh if [ "${LIB_RAND-}" ]; then return 0 fi LIB_RAND=1 rand() { seed="$1" range="$2" seed_file="$(mktemp)" _echo "$seed" | md5sum > "$seed_file" shuf -i "$range" -n 1 --random-source="$seed_file" } pick() { seed="$1" shift i="$(rand "$seed" "1-$#")" eval "_echo \"\$$i\"" } #!/bin/sh if [ "${LIB_LOG-}" ]; then return 0 fi LIB_LOG=1 tput() { if [ -n "$TERM" ]; then command tput "$@" fi } setaf() { tput setaf "$1" shift printf '%s' "$*" tput sgr0 } _echo() { printf '%s\n' "$*" } get_rand_color() { # 1-6 are regular and 9-14 are bright. # 1,2 and 9,10 are red and green but we use those for success and failure. pick "$*" 3 4 5 6 11 12 13 14 } echop() { prefix="$1" shift if [ "$#" -gt 0 ]; then printfp "$prefix" "%s\n" "$*" else printfp "$prefix" printf '\n' fi } printfp() {( prefix="$1" shift if [ -z "${COLOR:-}" ]; then COLOR="$(get_rand_color "$prefix")" fi printf '%s' "$(setaf "$COLOR" "$prefix")" if [ $# -gt 0 ]; then printf ': ' printf "$@" fi )} catp() { prefix="$1" shift sed "s/^/$(printfp "$prefix" '')/" } repeat() { char="$1" times="$2" seq -s "$char" "$times" | tr -d '[:digit:]' } strlen() { printf %s "$1" | wc -c } echoerr() { COLOR=1 echop err "$*" | humanpath>&2 } caterr() { COLOR=1 catp err "$@" | humanpath >&2 } printferr() { COLOR=1 printfp err "$@" | humanpath >&2 } logp() { echop "$@" | humanpath >&2 } logfp() { printfp "$@" | humanpath >&2 } logpcat() { catp "$@" | humanpath >&2 } log() { COLOR=5 logp log "$@" } logf() { COLOR=5 logfp log "$@" } logcat() { COLOR=5 catp log "$@" >&2 } warn() { COLOR=3 logp warn "$@" } warnf() { COLOR=3 logfp warn "$@" } sh_c() { COLOR=3 logp exec "$*" if [ -z "${DRY_RUN-}" ]; then eval "$@" fi } sudo_sh_c() { if [ "$(id -u)" -eq 0 ]; then sh_c "$@" elif command -v doas >/dev/null; then sh_c "doas $*" elif command -v sudo >/dev/null; then sh_c "sudo $*" elif command -v su >/dev/null; then sh_c "su root -c '$*'" else caterr <"$out" 2>&1 code="$?" set -e if [ "$code" -eq 0 ]; then return fi cat "$out" >&2 exit "$code" } echo_dur() { local dur=$1 local h=$((dur/60/60)) local m=$((dur/60%60)) local s=$((dur%60)) printf '%dh%dm%ds' "$h" "$m" "$s" } sponge() { dst="$1" tmp="$(mktemp)" cat > "$tmp" cat "$tmp" > "$dst" } stripansi() { # First regex gets rid of standard xterm escape sequences for controlling # visual attributes. # The second regex I'm not 100% sure, the reference says it selects the US # encoding but I'm not sure why that's necessary or why it always occurs # in tput sgr0 before the standard escape sequence. # See tput sgr0 | xxd sed -e $'s/\x1b\[[0-9;]*m//g' -e $'s/\x1b(.//g' } runtty() { case "$(uname)" in Darwin) script -q /dev/null "$@" ;; Linux) script -eqc "$*" ;; *) echoerr "runtty: unsupported OS $(uname)" return 1 esac } #!/bin/sh if [ "${LIB_FLAG-}" ]; then return 0 fi LIB_FLAG=1 # flag_parse implements a robust flag parser. # # For a full fledge example see ../examples/date.sh # # It differs from getopts(1) in that long form options are supported. Currently the only # deficiency is that short combined options are not supported like -xyzq. That would be # interpreted as a single -xyzq flag. The other deficiency is lack of support for short # flag syntax like -carg where the arg is not separated from the flag. This one is # unfixable I believe unfortunately but for combined short flags I have opened # https://github.com/terrastruct/ci/issues/6 # # flag_parse stores state in $FLAG, $FLAGRAW, $FLAGARG and $FLAGSHIFT. # FLAG contains the name of the flag without hyphens. # FLAGRAW contains the name of the flag as passed in with hyphens. # FLAGARG contains the argument for the flag if there was any. # If there was none, it will not be set. # FLAGSHIFT contains the number by which the arguments should be shifted to # start at the next flag/argument # # After each call check $FLAG for the name of the parsed flag. # If empty, then no more flags are left. # Still, call shift "$FLAGSHIFT" in case there was a -- # # If the argument for the flag is optional, then use ${FLAGARG-} to access # the argument if one was passed. Use ${FLAGARG+x} = x to check if it was set. # You only need to explicitly check if the flag was set if you care whether the user # explicitly passed the empty string as the argument. # # Otherwise, call one of the flag_*arg functions: # # If a flag requires an argument, call flag_reqarg # - $FLAGARG is guaranteed to be set after. # If a flag requires a non empty argument, call flag_nonemptyarg # - $FLAGARG is guaranteed to be set to a non empty string after. # If a flag should not be passed an argument, call flag_noarg # - $FLAGARG is guaranteed to be unset after. # # And then shift "$FLAGSHIFT" flag_parse() { case "${1-}" in -*=*) # Remove everything after first equal sign. FLAG="${1%%=*}" # Remove leading hyphens. FLAG="${FLAG#-}"; FLAG="${FLAG#-}" FLAGRAW="$(flag_fmt)" # Remove everything before first equal sign. FLAGARG="${1#*=}" FLAGSHIFT=1 ;; -) FLAG= FLAGRAW= unset FLAGARG FLAGSHIFT=0 ;; --) FLAG= FLAGRAW= unset FLAGARG FLAGSHIFT=1 ;; -*) # Remove leading hyphens. FLAG="${1#-}"; FLAG="${FLAG#-}" FLAGRAW=$(flag_fmt) unset FLAGARG FLAGSHIFT=1 if [ $# -gt 1 ]; then case "$2" in -) FLAGARG="$2" FLAGSHIFT=2 ;; -*) ;; *) FLAGARG="$2" FLAGSHIFT=2 ;; esac fi ;; *) FLAG= FLAGRAW= unset FLAGARG FLAGSHIFT=0 ;; esac return 0 } flag_reqarg() { if [ "${FLAGARG+x}" != x ]; then flag_errusage "flag $FLAGRAW requires an argument" fi } flag_nonemptyarg() { flag_reqarg if [ -z "$FLAGARG" ]; then flag_errusage "flag $FLAGRAW requires a non-empty argument" fi } flag_noarg() { if [ "$FLAGSHIFT" -eq 2 ]; then unset FLAGARG FLAGSHIFT=1 elif [ "${FLAGARG+x}" = x ]; then # Means an argument was passed via equal sign as in -$FLAG=$FLAGARG flag_errusage "flag $FLAGRAW does not accept an argument" fi } flag_errusage() { caterr < 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. 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 /usr/local/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. EOF } main() { if [ -n "${DEBUG-}" ]; then set -x fi METHOD=standalone while :; do flag_parse "$@" case "$FLAG" in h|help) help return 0 ;; 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 echoerr "$FLAGRAW is currently unimplemented" return 1 ;; prefix) flag_nonemptyarg && shift "$FLAGSHIFT" export PREFIX=$FLAGARG ;; force) flag_noarg && shift "$FLAGSHIFT" FORCE=1 ;; uninstall) flag_noarg && shift "$FLAGSHIFT" UNINSTALL=1 ;; '') shift "$FLAGSHIFT" break ;; *) flag_errusage "unrecognized flag $FLAGRAW" ;; esac done if [ $# -gt 0 ]; then flag_errusage "no arguments are accepted" fi REPO=${REPO:-terrastruct/d2} PREFIX=${PREFIX:-/usr/local} OS=$(os) ARCH=$(arch) CACHE_DIR=$(cache_dir) mkdir -p "$CACHE_DIR" INSTALL_DIR=$PREFIX/lib/d2 if [ -n "${UNINSTALL-}" ]; then uninstall return 0 fi VERSION=${VERSION:-latest} if [ "$VERSION" = latest ]; then header "fetching latest release info" fetch_release_info fi install } install() { install_d2 if [ "${TALA-}" ]; then # Run in subshell to avoid overwriting VERSION. ( install_tala ) fi COLOR=2 header success log "d2-$VERSION-$OS-$ARCH has been successfully installed into $PREFIX" if ! echo "$PATH" | grep -qF "$PREFIX/bin"; then logcat >&2 </dev/null; then INSTALLED_VERSION="$(d2 version)" if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then log "skipping installation as version $VERSION is already installed." return 0 fi log "uninstalling $INSTALLED_VERSION to install $VERSION" if ! uninstall_d2; then warn "failed to uninstall $INSTALLED_VERSION" fi fi header "installing d2-$VERSION" install_standalone_d2 } install_standalone_d2() { ARCHIVE="d2-$VERSION-$OS-$ARCH.tar.gz" log "installing standalone release $ARCHIVE from github" fetch_release_info asset_line=$(cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1) asset_url=$(sed -n $((asset_line-3))p "$RELEASE_INFO" | sed 's/^.*: "\(.*\)",$/\1/g') fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream' sh_c="sh_c" if ! is_prefix_writable; then sh_c="sudo_sh_c" fi "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'" } install_tala() { install_standalone_tala } install_standalone_tala() { REPO="${REPO_TALA:-terrastruct/TALA}" VERSION=$TALA RELEASE_INFO= fetch_release_info ARCHIVE="tala-$VERSION-$OS-$ARCH.tar.gz" log "installing standalone release $ARCHIVE from github" asset_line=$(cat "$RELEASE_INFO" | grep -n "$ARCHIVE" | cut -d: -f1 | head -n1) asset_url=$(sed -n $((asset_line-3))p "$RELEASE_INFO" | sed 's/^.*: "\(.*\)",$/\1/g') fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream' sh_c="sh_c" if ! is_prefix_writable; then sh_c="sudo_sh_c" fi "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'" } uninstall() { if ! command -v d2 >/dev/null; then echoerr "no version of d2 installed" return 1 fi INSTALLED_VERSION="$(d2 --version)" if ! uninstall_d2; then echoerr "failed to uninstall $INSTALLED_VERSION" return 1 fi if [ "${TALA-}" ]; then if ! command -v d2plugin-tala >/dev/null; then echoerr "no version of tala installed" return 1 fi INSTALLED_VERSION="$(d2plugin-tala --version)" if ! uninstall_tala; then echoerr "failed to uninstall tala $INSTALLED_VERSION" return 1 fi fi return 0 } uninstall_d2() { header "uninstalling d2-$INSTALLED_VERSION" uninstall_standalone_d2 } uninstall_standalone_d2() { log "uninstalling standalone release of d2-$INSTALLED_VERSION" if [ ! -e "$INSTALL_DIR/d2-$INSTALLED_VERSION" ]; then echoerr "missing standalone install release directory $INSTALL_DIR/d2-$INSTALLED_VERSION" return 1 fi sh_c="sh_c" if ! is_prefix_writable; then sh_c="sudo_sh_c" fi "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'" "$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION" } uninstall_tala() { uninstall_standalone_tala } uninstall_standalone_tala() { log "uninstalling standalone release tala-$INSTALLED_VERSION" if [ ! -e "$INSTALL_DIR/tala-$INSTALLED_VERSION" ]; then echoerr "missing standalone install release directory $INSTALL_DIR/tala-$INSTALLED_VERSION" return 1 fi sh_c="sh_c" if ! is_prefix_writable; then sh_c="sudo_sh_c" fi "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'" "$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION" } is_prefix_writable() { sh_c "mkdir -p '$INSTALL_DIR' 2>/dev/null" || true # The reason for checking whether bin is writable specifically is that on macOS you have # /usr/local owned by root but you don't need root to write to its subdirectories which # is all we want to do. if [ ! -w "$PREFIX/bin" ]; then return 0 fi } 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=$(mktemp -d)/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 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" } main "$@"