install.sh: Improve default $PREFIX behaviour

As before we default to /usr/local but now we switch to ~/.local if
/usr/local if not accessible instead of prompting for sudo. Instead
of having an exception for macOS/arm64.

Of course if a user wants to install in /usr/local anyway, they can
still pass --prefix /usr/local to force installation into /usr/local
and prompt for sudo.
This commit is contained in:
Anmol Sethi 2022-12-05 21:51:46 -08:00
parent c0b3badbde
commit 6e52966b52
No known key found for this signature in database
GPG key ID: 25BC68888A99A8BA
13 changed files with 254 additions and 208 deletions

View file

@ -10,10 +10,11 @@ VERSION=$VERSION sh_c eval "'$HW_BUILD_DIR/README.md.sh'" \> "'$HW_BUILD_DIR/REA
sh_c rm -f "$HW_BUILD_DIR/README.md.sh" sh_c rm -f "$HW_BUILD_DIR/README.md.sh"
sh_c find "$HW_BUILD_DIR" -exec touch {} \\\; sh_c find "$HW_BUILD_DIR" -exec touch {} \\\;
export GOOS=$(goos "$OS") ensure_goos
export GOARCH="$ARCH" ensure_goarch
sh_c mkdir -p "$HW_BUILD_DIR/bin" sh_c mkdir -p "$HW_BUILD_DIR/bin"
sh_c go build -ldflags "'-X oss.terrastruct.com/d2/lib/version.Version=$VERSION'" \ sh_c CGO_ENABLED=0 go build \
-ldflags "'-X oss.terrastruct.com/d2/lib/version.Version=$VERSION'" \
-o "$HW_BUILD_DIR/bin/d2" . -o "$HW_BUILD_DIR/bin/d2" .
ARCHIVE=$PWD/$ARCHIVE ARCHIVE=$PWD/$ARCHIVE

View file

@ -14,7 +14,7 @@ help() {
fi fi
cat <<EOF cat <<EOF
usage: $arg0 [-d|--dry-run] [--version vX.X.X] [--edge] [--method detect] [--prefix /usr/local] usage: $arg0 [-d|--dry-run] [--version vX.X.X] [--edge] [--method detect] [--prefix path]
[--tala latest] [--force] [--uninstall] [--tala latest] [--force] [--uninstall]
install.sh automates the installation of D2 onto your system. It currently only supports install.sh automates the installation of D2 onto your system. It currently only supports
@ -24,6 +24,9 @@ docs for --detect below for more information
If you pass --edge, it will clone the source, build a release and install from it. If you pass --edge, it will clone the source, build a release and install from it.
--edge is incompatible with --tala and currently unimplemented. --edge is incompatible with --tala and currently unimplemented.
\$PREFIX in the docs below refers to the path set by --prefix. See docs on the --prefix
flag below for the default.
Flags: Flags:
-d, --dry-run -d, --dry-run
@ -51,30 +54,31 @@ Flags:
So far it only detects macOS and automatically uses homebrew. So far it only detects macOS and automatically uses homebrew.
- homebrew uses https://brew.sh/ which is a macOS and Linux package manager. - homebrew uses https://brew.sh/ which is a macOS and Linux package manager.
- standalone installs a standalone release archive into the unix hierarchy path - standalone installs a standalone release archive into the unix hierarchy path
specified by --prefix which defaults to /usr/local specified by --prefix
Ensure /usr/local/bin is in your \$PATH to use it.
--prefix /usr/local --prefix path
Controls the unix hierarchy path into which standalone releases are installed. Controls the unix hierarchy path into which standalone releases are installed.
Defaults to /usr/local. You may also want to use ~/.local to avoid needing sudo. Defaults to /usr/local or ~/.local if /usr/local is not writable by the current user.
We use ~/.local by default on arm64 macOS machines as SIP now disables access to Remember that whatever you use, you must have the bin directory of your prefix path in
/usr/local. Remember that whatever you use, you must have the bin directory of your \$PATH to execute the d2 binary. For example, if my prefix directory is /usr/local then
prefix path in \$PATH to execute the d2 binary. For example, if my prefix directory is my \$PATH must contain /usr/local/bin.
/usr/local then my \$PATH must contain /usr/local/bin. You may also need to include \$PREFIX/share/man into \$MANPATH.
install.sh will tell you whether \$PATH or \$MANPATH need to be updated after successful
installation.
--tala [latest] --tala [latest]
Install Terrastruct's closed source TALA for improved layouts. Install Terrastruct's closed source TALA for improved layouts.
See https://github.com/terrastruct/tala See https://github.com/terrastruct/tala
It optionally takes an argument of the TALA version to install. It optionally takes an argument of the TALA version to install.
Installation obeys all other flags, just like the installation of d2. For example, Installation obeys all other flags, just like the installation of d2. For example,
the d2plugin-tala binary will be installed into /usr/local/bin/d2plugin-tala the d2plugin-tala binary will be installed into \$PREFIX/bin/d2plugin-tala
warn: The version may not be obeyed with package manager installations. Use warn: The version may not be obeyed with package manager installations. Use
--method=standalone to enforce the version. --method=standalone to enforce the version.
--force: --force:
Force installation over the existing version even if they match. It will attempt a Force installation over the existing version even if they match. It will attempt a
uninstall first before installing the new version. The installed release tree uninstall first before installing the new version. The installed release tree
will be deleted from /usr/local/lib/d2/d2-<VERSION> but the release archive in will be deleted from \$PREFIX/lib/d2/d2-<VERSION> but the release archive in
~/.cache/d2/release will remain. ~/.cache/d2/release will remain.
--uninstall: --uninstall:
@ -85,7 +89,7 @@ Flags:
note: tala will also be uninstalled if installed. note: tala will also be uninstalled if installed.
All downloaded archives are cached into ~/.cache/d2/release. use \$XDG_CACHE_HOME to change 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-<VERSION> path of the cached assets. Release archives are unarchived into \$PREFIX/lib/d2/d2-<VERSION>
note: Deleting the unarchived releases will cause --uninstall to stop working. note: Deleting the unarchived releases will cause --uninstall to stop working.
@ -150,13 +154,9 @@ main() {
fi fi
REPO=${REPO:-terrastruct/d2} REPO=${REPO:-terrastruct/d2}
OS=$(os) ensure_os
ARCH=$(arch) ensure_arch
if [ -z "${PREFIX-}" -a "$OS" = macos -a "$ARCH" = arm64 ]; then ensure_prefix
# M1 Mac's do not allow modifications to /usr/local even with sudo.
PREFIX=$HOME/.local
fi
PREFIX=${PREFIX:-/usr/local}
CACHE_DIR=$(cache_dir) CACHE_DIR=$(cache_dir)
mkdir -p "$CACHE_DIR" mkdir -p "$CACHE_DIR"
METHOD=${METHOD:-detect} METHOD=${METHOD:-detect}
@ -318,11 +318,7 @@ install_d2_standalone() {
asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"') 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' fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream'
sh_c="sh_c" ensure_prefix_sh_c
if ! is_prefix_writable; then
sh_c="sudo_sh_c"
fi
"$sh_c" mkdir -p "'$INSTALL_DIR'" "$sh_c" mkdir -p "'$INSTALL_DIR'"
"$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE"
"$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'" "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
@ -365,11 +361,7 @@ install_tala_standalone() {
fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream' fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream'
sh_c="sh_c" ensure_prefix_sh_c
if ! is_prefix_writable; then
sh_c="sudo_sh_c"
fi
"$sh_c" mkdir -p "'$INSTALL_DIR'" "$sh_c" mkdir -p "'$INSTALL_DIR'"
"$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE"
"$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'" "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
@ -417,11 +409,7 @@ uninstall_d2_standalone() {
return 1 return 1
fi fi
sh_c="sh_c" ensure_prefix_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" sh -c "'cd \"$INSTALL_DIR/d2-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'"
"$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION" "$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION"
} }
@ -439,11 +427,7 @@ uninstall_tala_standalone() {
return 1 return 1
fi fi
sh_c="sh_c" ensure_prefix_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" sh -c "'cd \"$INSTALL_DIR/tala-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'"
"$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION" "$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION"
} }
@ -452,13 +436,6 @@ uninstall_tala_brew() {
sh_c brew remove tala sh_c brew remove tala
} }
is_prefix_writable() {
# The reason for checking whether $INSTALL_DIR is writable 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.
is_writable_dir "$INSTALL_DIR"
}
cache_dir() { cache_dir() {
if [ -n "${XDG_CACHE_HOME-}" ]; then if [ -n "${XDG_CACHE_HOME-}" ]; then
echo "$XDG_CACHE_HOME/d2/release" echo "$XDG_CACHE_HOME/d2/release"

View file

@ -109,11 +109,14 @@ main() {
VERSION=${VERSION:-$(git_describe_ref)} VERSION=${VERSION:-$(git_describe_ref)}
BUILD_DIR=ci/release/build/$VERSION BUILD_DIR=ci/release/build/$VERSION
if [ -n "${HOST_ONLY-}" ]; then if [ -n "${HOST_ONLY-}" ]; then
runjob $(os)-$(arch) "OS=$(os) ARCH=$(arch) build" OS=$(os)
ARCH=$(arch)
runjob "$OS-$ARCH" "build"
if [ -n "${INSTALL-}" ]; then if [ -n "${INSTALL-}" ]; then
( sh_c make -sC "ci/release/build/$VERSION/$(os)-$(arch)/d2-$VERSION" install) sh_c make -sC "ci/release/build/$VERSION/$(os)-$(arch)/d2-$VERSION" install
elif [ -n "${UNINSTALL-}" ]; then elif [ -n "${UNINSTALL-}" ]; then
( sh_c make -sC "ci/release/build/$VERSION/$(os)-$(arch)/d2-$VERSION" uninstall) sh_c make -sC "ci/release/build/$VERSION/$(os)-$(arch)/d2-$VERSION" uninstall
fi fi
return 0 return 0
fi fi
@ -123,7 +126,7 @@ main() {
runjob macos-amd64 'OS=macos ARCH=amd64 build' & runjob macos-amd64 'OS=macos ARCH=amd64 build' &
runjob macos-arm64 'OS=macos ARCH=arm64 build' & runjob macos-arm64 'OS=macos ARCH=arm64 build' &
runjob windows-amd64 'OS=windows ARCH=amd64 build' & runjob windows-amd64 'OS=windows ARCH=amd64 build' &
runjob windows-arm64 'OS=macos ARCH=arm64 build' & runjob windows-arm64 'OS=windows ARCH=arm64 build' &
waitjobs waitjobs
} }
@ -189,8 +192,7 @@ build_local() {
build_remote_macos() { build_remote_macos() {
sh_c lockfile_ssh "$REMOTE_HOST" .d2-build-lock sh_c lockfile_ssh "$REMOTE_HOST" .d2-build-lock
sh_c ssh "$REMOTE_HOST" mkdir -p src sh_c gitsync "$REMOTE_HOST" src/d2
sh_c rsync --archive --human-readable --delete ./ "$REMOTE_HOST:src/d2/"
sh_c ssh "$REMOTE_HOST" "COLOR=${COLOR-} \ sh_c ssh "$REMOTE_HOST" "COLOR=${COLOR-} \
TERM=${TERM-} \ TERM=${TERM-} \
DRY_RUN=${DRY_RUN-} \ DRY_RUN=${DRY_RUN-} \
@ -207,8 +209,7 @@ PATH=\\\"/usr/local/bin:/usr/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin\\\$
build_remote_linux() { build_remote_linux() {
sh_c lockfile_ssh "$REMOTE_HOST" .d2-build-lock sh_c lockfile_ssh "$REMOTE_HOST" .d2-build-lock
sh_c ssh "$REMOTE_HOST" mkdir -p src sh_c gitsync "$REMOTE_HOST" src/d2
sh_c rsync --archive --human-readable --delete ./ "$REMOTE_HOST:src/d2/"
sh_c ssh "$REMOTE_HOST" "COLOR=${COLOR-} \ sh_c ssh "$REMOTE_HOST" "COLOR=${COLOR-} \
TERM=${TERM-} \ TERM=${TERM-} \
DRY_RUN=${DRY_RUN-} \ DRY_RUN=${DRY_RUN-} \

View file

@ -253,7 +253,7 @@ init_remote_macos() {
sh_c ssh "$REMOTE_HOST" '"/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""' sh_c ssh "$REMOTE_HOST" '"/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""'
sh_c ssh "$REMOTE_HOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew update' sh_c ssh "$REMOTE_HOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew update'
sh_c ssh "$REMOTE_HOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew upgrade' sh_c ssh "$REMOTE_HOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew upgrade'
sh_c ssh "$REMOTE_HOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew install go' sh_c ssh "$REMOTE_HOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew install go rsync'
} }
main "$@" main "$@"

View file

@ -57,6 +57,11 @@ PRs when it's a visual change.
- `d2` now lives in the root folder of the repository instead of as a subcommand. So you - `d2` now lives in the root folder of the repository instead of as a subcommand. So you
can now run `go install oss.terrastruct.com/d2@latest` to install from source. can now run `go install oss.terrastruct.com/d2@latest` to install from source.
[#290](https://github.com/terrastruct/d2/pull/290) [#290](https://github.com/terrastruct/d2/pull/290)
- `install.sh` defaults to installation to `/usr/local` as before but now if
`/usr/local` is not accessible to the current user, it will use `~/.local`
instead of prompting for sudo. You can pass `--prefix /usr/local` to force
installation into `/usr/local` with a prompt for sudo.
[#372](https://github.com/terrastruct/d2/pull/372)
#### Bugfixes ⛑️ #### Bugfixes ⛑️

View file

@ -6,12 +6,10 @@ all:
( . ./scripts/lib.sh && echoerr "You must provide a target of install or uninstall for this Makefile" ) ( . ./scripts/lib.sh && echoerr "You must provide a target of install or uninstall for this Makefile" )
exit 1 exit 1
PREFIX = $(DESTDIR)/usr/local
.PHONY: install .PHONY: install
install: install:
PREFIX='$(PREFIX)' ./scripts/install.sh ./scripts/install.sh
.PHONY: uninstall .PHONY: uninstall
uninstall: uninstall:
PREFIX='$(PREFIX)' ./scripts/uninstall.sh ./scripts/uninstall.sh

View file

@ -13,16 +13,17 @@ version: $VERSION
\`\`\`sh \`\`\`sh
make install DRY_RUN=1 make install DRY_RUN=1
# If it looks right, run: # If it looks right, run:
make install # make install
\`\`\` \`\`\`
Pass \`PREFIX=whatever\` to change the installation prefix. Pass \`PREFIX=somepath\` to change the installation prefix from the default which is
\`/usr/local\` or \`~/.local\` if \`/usr/local\` is not writable with the current user.
## Uninstall ## Uninstall
\`\`\`sh \`\`\`sh
make uninstall DRY_RUN=1 make uninstall DRY_RUN=1
# If it looks right, run: # If it looks right, run:
make uninstall # make uninstall
\`\`\` \`\`\`
EOF EOF

View file

@ -4,16 +4,7 @@ cd -- "$(dirname "$0")/.."
. ./scripts/lib.sh . ./scripts/lib.sh
main() { main() {
if [ -z "${PREFIX-}" ]; then ensure_prefix_sh_c
echoerr "\$PREFIX must be set to a unix prefix directory in which to install d2 like /usr/local"
return 1
fi
sh_c="sh_c"
if ! is_writable_dir "$PREFIX/bin"; then
sh_c="sudo_sh_c"
fi
"$sh_c" mkdir -p "$PREFIX/bin" "$sh_c" mkdir -p "$PREFIX/bin"
"$sh_c" install ./bin/d2 "$PREFIX/bin/d2" "$sh_c" install ./bin/d2 "$PREFIX/bin/d2"
"$sh_c" mkdir -p "$PREFIX/share/man/man1" "$sh_c" mkdir -p "$PREFIX/share/man/man1"

View file

@ -23,11 +23,14 @@ pick() {
shift shift
seed_file="$(mktemp)" seed_file="$(mktemp)"
echo "$seed" >"$seed_file"
# We add 16 more bytes to the seed file for sufficient entropy. Otherwise both Cygwin's # We add 32 more bytes to the seed file for sufficient entropy. Otherwise both Cygwin's
# and MinGW's sort for example complains about the lack of entropy on stderr and writes # and MinGW's sort for example complains about the lack of entropy on stderr and writes
# nothing to stdout. I'm sure there are more platforms that would too. # nothing to stdout. I'm sure there are more platforms that would too.
echo "================" >"$seed_file" #
# We also limit to a max of 32 bytes as otherwise macOS's sort complains that the random
# seed is too large. Probably more platforms too.
(echo "$seed" && echo "================================") | head -c32 >"$seed_file"
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
echo "$1" echo "$1"
@ -89,16 +92,20 @@ _echo() {
printf '%s\n' "$*" printf '%s\n' "$*"
} }
# 1-6 are regular and 9-14 are bright.
get_rand_color() { get_rand_color() {
colors="" if [ "${TERM_COLORS+x}" != x ]; then
ncolors=$(command tput colors) TERM_COLORS=""
export TERM_COLORS
ncolors=$(TERM=${TERM:-xterm-256color} command tput colors)
if [ "$ncolors" -ge 8 ]; then if [ "$ncolors" -ge 8 ]; then
colors="$colors 1 2 3 4 5 6" # 1-6 are regular
TERM_COLORS="$TERM_COLORS 1 2 3 4 5 6"
elif [ "$ncolors" -ge 16 ]; then elif [ "$ncolors" -ge 16 ]; then
colors="$colors 9 10 11 12 13 14" # 9-14 are bright.
TERM_COLORS="$TERM_COLORS 9 10 11 12 13 14"
fi fi
pick "$*" $colors fi
pick "$*" $TERM_COLORS
} }
echop() { echop() {
@ -117,14 +124,12 @@ printfp() {(
prefix="$1" prefix="$1"
shift shift
if [ -z "${FGCOLOR-}" ]; then _FGCOLOR=${FGCOLOR:-$(get_rand_color "$prefix")}
FGCOLOR="$(get_rand_color "$prefix")"
fi
should_color || true should_color || true
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
printf '%s' "$(COLOR=$__COLOR setaf "$FGCOLOR" "$prefix")" printf '%s' "$(COLOR=$__COLOR setaf "$_FGCOLOR" "$prefix")"
else else
printf '%s: %s\n' "$(COLOR=$__COLOR setaf "$FGCOLOR" "$prefix")" "$(printf "$@")" printf '%s: %s\n' "$(COLOR=$__COLOR setaf "$_FGCOLOR" "$prefix")" "$(printf "$@")"
fi fi
)} )}
@ -302,36 +307,60 @@ capcode() {
code=$? code=$?
set -e set -e
} }
strjoin() {
(IFS="$1"; shift; echo "$*")
}
#!/bin/sh #!/bin/sh
if [ "${LIB_RELEASE-}" ]; then if [ "${LIB_RELEASE-}" ]; then
return 0 return 0
fi fi
LIB_RELEASE=1 LIB_RELEASE=1
goos() { ensure_goos() {
case $1 in if [ -n "${GOOS-}" ]; then
macos) echo darwin;; return
*) echo $1;; fi
ensure_os
case "$OS" in
macos) export GOOS=darwin;;
*) export GOOS=$1;;
esac esac
} }
os() { ensure_goarch() {
if [ -n "${GOARCH-}" ]; then
return
fi
ensure_arch
case "$ARCH" in
*) export GOARCH=$1;;
esac
}
ensure_os() {
if [ -n "${OS-}" ]; then
return
fi
uname=$(uname) uname=$(uname)
case $uname in case $uname in
Linux) echo linux;; Linux) OS=linux;;
Darwin) echo macos;; Darwin) OS=macos;;
FreeBSD) echo freebsd;; FreeBSD) OS=freebsd;;
CYGWIN_NT*|MINGW32_NT*) echo windows;; CYGWIN_NT*|MINGW32_NT*) OS=windows;;
*) echo "$uname";; *) OS=$uname;;
esac esac
} }
arch() { ensure_arch() {
if [ -n "${ARCH-}" ]; then
return
fi
uname_m=$(uname -m) uname_m=$(uname -m)
case $uname_m in case $uname_m in
aarch64) echo arm64;; aarch64) ARCH=arm64;;
x86_64) echo amd64;; x86_64) ARCH=amd64;;
*) echo "$uname_m";; *) ARCH=$uname_m;;
esac esac
} }
@ -350,9 +379,34 @@ manpath() {
} }
is_writable_dir() { is_writable_dir() {
# The path has to exist for -w to succeed. # If it can be created, we can use it.
sh_c "mkdir -p '$1' 2>/dev/null" || true sh_c "mkdir -p '$1' 2>/dev/null"
if [ ! -w "$1" ]; then }
return 1
ensure_prefix() {
if [ -n "${PREFIX-}" ]; then
return
fi
# The reason for checking whether bin is writable 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
# need to do.
if ! is_writable_dir "/usr/local/bin"; then
# This also handles M1 Mac's which do not allow modifications to /usr/local even
# with sudo.
PREFIX=$HOME/.local
else
PREFIX=/usr/local
fi
}
ensure_prefix_sh_c() {
ensure_prefix
sh_c="sh_c"
# The reason for checking whether bin is writable 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
# need to do.
if ! is_writable_dir "$PREFIX/bin"; then
sh_c="sudo_sh_c"
fi fi
} }

View file

@ -4,16 +4,7 @@ cd -- "$(dirname "$0")/.."
. ./scripts/lib.sh . ./scripts/lib.sh
main() { main() {
if [ -z "${PREFIX-}" ]; then ensure_prefix_sh_c
echoerr "\$PREFIX must be set to a unix prefix directory from which to uninstall d2 like /usr/local"
return 1
fi
sh_c="sh_c"
if ! is_writable_dir "$PREFIX/bin"; then
sh_c="sudo_sh_c"
fi
"$sh_c" rm -f "$PREFIX/bin/d2" "$sh_c" rm -f "$PREFIX/bin/d2"
"$sh_c" rm -f "$PREFIX/share/man/man1/d2.1" "$sh_c" rm -f "$PREFIX/share/man/man1/d2.1"
} }

2
ci/sub

@ -1 +1 @@
Subproject commit ad50bf9f1e302bfb1a4d3c6bb821f90979083265 Subproject commit 153c562ec591b7d32e265fabbd426a6b69e4fde8

View file

@ -47,9 +47,9 @@ We're careful shell programmers and are aware of the many footguns of the Unix s
script was written carefully and with detail. For example, it is not vulnerable to partial script was written carefully and with detail. For example, it is not vulnerable to partial
execution and the entire script runs with `set -eu` and very meticulous quoting. execution and the entire script runs with `set -eu` and very meticulous quoting.
It follows the XDG standards, installs `d2` properly into a Unix hierarchy path (defaulting It follows the XDG standards, installs `d2` properly into a Unix hierarchy path
to /usr/local though you can use ~/.local to avoid sudo if you'd like) and allows for easy (`/usr/local` unless `/usr/local` requires sudo in which case `~/.local` is used) and
uninstall. allows for easy uninstall. You can easily adjust the used path with `--prefix`.
Some other niceties are that it'll tell you if you need to adjust `$PATH` or `$MANPATH` to Some other niceties are that it'll tell you if you need to adjust `$PATH` or `$MANPATH` to
access `d2` and its manpages. It can also install access `d2` and its manpages. It can also install
@ -126,10 +126,6 @@ The install script places the standalone release into `$PREFIX/lib/d2/d2-<versio
and we recommend doing the same with manually installed releases so that you and we recommend doing the same with manually installed releases so that you
know where the release directory is for easy uninstall. know where the release directory is for easy uninstall.
> warn: Our binary releases aren't fully static like normal Go binaries due to the C
> dependency on v8go for executing dagre. If you're on an older system with an old
> libc, you'll want to install from source.
## From source ## From source
You can always install from source: You can always install from source:

View file

@ -28,11 +28,14 @@ pick() {
shift shift
seed_file="$(mktemp)" seed_file="$(mktemp)"
echo "$seed" >"$seed_file"
# We add 16 more bytes to the seed file for sufficient entropy. Otherwise both Cygwin's # We add 32 more bytes to the seed file for sufficient entropy. Otherwise both Cygwin's
# and MinGW's sort for example complains about the lack of entropy on stderr and writes # and MinGW's sort for example complains about the lack of entropy on stderr and writes
# nothing to stdout. I'm sure there are more platforms that would too. # nothing to stdout. I'm sure there are more platforms that would too.
echo "================" >"$seed_file" #
# We also limit to a max of 32 bytes as otherwise macOS's sort complains that the random
# seed is too large. Probably more platforms too.
(echo "$seed" && echo "================================") | head -c32 >"$seed_file"
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
echo "$1" echo "$1"
@ -94,16 +97,20 @@ _echo() {
printf '%s\n' "$*" printf '%s\n' "$*"
} }
# 1-6 are regular and 9-14 are bright.
get_rand_color() { get_rand_color() {
colors="" if [ "${TERM_COLORS+x}" != x ]; then
ncolors=$(command tput colors) TERM_COLORS=""
export TERM_COLORS
ncolors=$(TERM=${TERM:-xterm-256color} command tput colors)
if [ "$ncolors" -ge 8 ]; then if [ "$ncolors" -ge 8 ]; then
colors="$colors 1 2 3 4 5 6" # 1-6 are regular
TERM_COLORS="$TERM_COLORS 1 2 3 4 5 6"
elif [ "$ncolors" -ge 16 ]; then elif [ "$ncolors" -ge 16 ]; then
colors="$colors 9 10 11 12 13 14" # 9-14 are bright.
TERM_COLORS="$TERM_COLORS 9 10 11 12 13 14"
fi fi
pick "$*" $colors fi
pick "$*" $TERM_COLORS
} }
echop() { echop() {
@ -122,14 +129,12 @@ printfp() {(
prefix="$1" prefix="$1"
shift shift
if [ -z "${FGCOLOR-}" ]; then _FGCOLOR=${FGCOLOR:-$(get_rand_color "$prefix")}
FGCOLOR="$(get_rand_color "$prefix")"
fi
should_color || true should_color || true
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
printf '%s' "$(COLOR=$__COLOR setaf "$FGCOLOR" "$prefix")" printf '%s' "$(COLOR=$__COLOR setaf "$_FGCOLOR" "$prefix")"
else else
printf '%s: %s\n' "$(COLOR=$__COLOR setaf "$FGCOLOR" "$prefix")" "$(printf "$@")" printf '%s: %s\n' "$(COLOR=$__COLOR setaf "$_FGCOLOR" "$prefix")" "$(printf "$@")"
fi fi
)} )}
@ -307,6 +312,10 @@ capcode() {
code=$? code=$?
set -e set -e
} }
strjoin() {
(IFS="$1"; shift; echo "$*")
}
#!/bin/sh #!/bin/sh
if [ "${LIB_FLAG-}" ]; then if [ "${LIB_FLAG-}" ]; then
return 0 return 0
@ -444,30 +453,50 @@ if [ "${LIB_RELEASE-}" ]; then
fi fi
LIB_RELEASE=1 LIB_RELEASE=1
goos() { ensure_goos() {
case $1 in if [ -n "${GOOS-}" ]; then
macos) echo darwin;; return
*) echo $1;; fi
ensure_os
case "$OS" in
macos) export GOOS=darwin;;
*) export GOOS=$1;;
esac esac
} }
os() { ensure_goarch() {
if [ -n "${GOARCH-}" ]; then
return
fi
ensure_arch
case "$ARCH" in
*) export GOARCH=$1;;
esac
}
ensure_os() {
if [ -n "${OS-}" ]; then
return
fi
uname=$(uname) uname=$(uname)
case $uname in case $uname in
Linux) echo linux;; Linux) OS=linux;;
Darwin) echo macos;; Darwin) OS=macos;;
FreeBSD) echo freebsd;; FreeBSD) OS=freebsd;;
CYGWIN_NT*|MINGW32_NT*) echo windows;; CYGWIN_NT*|MINGW32_NT*) OS=windows;;
*) echo "$uname";; *) OS=$uname;;
esac esac
} }
arch() { ensure_arch() {
if [ -n "${ARCH-}" ]; then
return
fi
uname_m=$(uname -m) uname_m=$(uname -m)
case $uname_m in case $uname_m in
aarch64) echo arm64;; aarch64) ARCH=arm64;;
x86_64) echo amd64;; x86_64) ARCH=amd64;;
*) echo "$uname_m";; *) ARCH=$uname_m;;
esac esac
} }
@ -486,10 +515,35 @@ manpath() {
} }
is_writable_dir() { is_writable_dir() {
# The path has to exist for -w to succeed. # If it can be created, we can use it.
sh_c "mkdir -p '$1' 2>/dev/null" || true sh_c "mkdir -p '$1' 2>/dev/null"
if [ ! -w "$1" ]; then }
return 1
ensure_prefix() {
if [ -n "${PREFIX-}" ]; then
return
fi
# The reason for checking whether bin is writable 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
# need to do.
if ! is_writable_dir "/usr/local/bin"; then
# This also handles M1 Mac's which do not allow modifications to /usr/local even
# with sudo.
PREFIX=$HOME/.local
else
PREFIX=/usr/local
fi
}
ensure_prefix_sh_c() {
ensure_prefix
sh_c="sh_c"
# The reason for checking whether bin is writable 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
# need to do.
if ! is_writable_dir "$PREFIX/bin"; then
sh_c="sudo_sh_c"
fi fi
} }
#!/bin/sh #!/bin/sh
@ -503,7 +557,7 @@ help() {
fi fi
cat <<EOF cat <<EOF
usage: $arg0 [-d|--dry-run] [--version vX.X.X] [--edge] [--method detect] [--prefix /usr/local] usage: $arg0 [-d|--dry-run] [--version vX.X.X] [--edge] [--method detect] [--prefix path]
[--tala latest] [--force] [--uninstall] [--tala latest] [--force] [--uninstall]
install.sh automates the installation of D2 onto your system. It currently only supports install.sh automates the installation of D2 onto your system. It currently only supports
@ -513,6 +567,9 @@ docs for --detect below for more information
If you pass --edge, it will clone the source, build a release and install from it. If you pass --edge, it will clone the source, build a release and install from it.
--edge is incompatible with --tala and currently unimplemented. --edge is incompatible with --tala and currently unimplemented.
\$PREFIX in the docs below refers to the path set by --prefix. See docs on the --prefix
flag below for the default.
Flags: Flags:
-d, --dry-run -d, --dry-run
@ -540,30 +597,31 @@ Flags:
So far it only detects macOS and automatically uses homebrew. So far it only detects macOS and automatically uses homebrew.
- homebrew uses https://brew.sh/ which is a macOS and Linux package manager. - homebrew uses https://brew.sh/ which is a macOS and Linux package manager.
- standalone installs a standalone release archive into the unix hierarchy path - standalone installs a standalone release archive into the unix hierarchy path
specified by --prefix which defaults to /usr/local specified by --prefix
Ensure /usr/local/bin is in your \$PATH to use it.
--prefix /usr/local --prefix path
Controls the unix hierarchy path into which standalone releases are installed. Controls the unix hierarchy path into which standalone releases are installed.
Defaults to /usr/local. You may also want to use ~/.local to avoid needing sudo. Defaults to /usr/local or ~/.local if /usr/local is not writable by the current user.
We use ~/.local by default on arm64 macOS machines as SIP now disables access to Remember that whatever you use, you must have the bin directory of your prefix path in
/usr/local. Remember that whatever you use, you must have the bin directory of your \$PATH to execute the d2 binary. For example, if my prefix directory is /usr/local then
prefix path in \$PATH to execute the d2 binary. For example, if my prefix directory is my \$PATH must contain /usr/local/bin.
/usr/local then my \$PATH must contain /usr/local/bin. You may also need to include \$PREFIX/share/man into \$MANPATH.
install.sh will tell you whether \$PATH or \$MANPATH need to be updated after successful
installation.
--tala [latest] --tala [latest]
Install Terrastruct's closed source TALA for improved layouts. Install Terrastruct's closed source TALA for improved layouts.
See https://github.com/terrastruct/tala See https://github.com/terrastruct/tala
It optionally takes an argument of the TALA version to install. It optionally takes an argument of the TALA version to install.
Installation obeys all other flags, just like the installation of d2. For example, Installation obeys all other flags, just like the installation of d2. For example,
the d2plugin-tala binary will be installed into /usr/local/bin/d2plugin-tala the d2plugin-tala binary will be installed into \$PREFIX/bin/d2plugin-tala
warn: The version may not be obeyed with package manager installations. Use warn: The version may not be obeyed with package manager installations. Use
--method=standalone to enforce the version. --method=standalone to enforce the version.
--force: --force:
Force installation over the existing version even if they match. It will attempt a Force installation over the existing version even if they match. It will attempt a
uninstall first before installing the new version. The installed release tree uninstall first before installing the new version. The installed release tree
will be deleted from /usr/local/lib/d2/d2-<VERSION> but the release archive in will be deleted from \$PREFIX/lib/d2/d2-<VERSION> but the release archive in
~/.cache/d2/release will remain. ~/.cache/d2/release will remain.
--uninstall: --uninstall:
@ -574,7 +632,7 @@ Flags:
note: tala will also be uninstalled if installed. note: tala will also be uninstalled if installed.
All downloaded archives are cached into ~/.cache/d2/release. use \$XDG_CACHE_HOME to change 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-<VERSION> path of the cached assets. Release archives are unarchived into \$PREFIX/lib/d2/d2-<VERSION>
note: Deleting the unarchived releases will cause --uninstall to stop working. note: Deleting the unarchived releases will cause --uninstall to stop working.
@ -639,13 +697,9 @@ main() {
fi fi
REPO=${REPO:-terrastruct/d2} REPO=${REPO:-terrastruct/d2}
OS=$(os) ensure_os
ARCH=$(arch) ensure_arch
if [ -z "${PREFIX-}" -a "$OS" = macos -a "$ARCH" = arm64 ]; then ensure_prefix
# M1 Mac's do not allow modifications to /usr/local even with sudo.
PREFIX=$HOME/.local
fi
PREFIX=${PREFIX:-/usr/local}
CACHE_DIR=$(cache_dir) CACHE_DIR=$(cache_dir)
mkdir -p "$CACHE_DIR" mkdir -p "$CACHE_DIR"
METHOD=${METHOD:-detect} METHOD=${METHOD:-detect}
@ -807,11 +861,7 @@ install_d2_standalone() {
asset_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"') 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' fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream'
sh_c="sh_c" ensure_prefix_sh_c
if ! is_prefix_writable; then
sh_c="sudo_sh_c"
fi
"$sh_c" mkdir -p "'$INSTALL_DIR'" "$sh_c" mkdir -p "'$INSTALL_DIR'"
"$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE"
"$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'" "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
@ -854,11 +904,7 @@ install_tala_standalone() {
fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream' fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream'
sh_c="sh_c" ensure_prefix_sh_c
if ! is_prefix_writable; then
sh_c="sudo_sh_c"
fi
"$sh_c" mkdir -p "'$INSTALL_DIR'" "$sh_c" mkdir -p "'$INSTALL_DIR'"
"$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE"
"$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'" "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'"
@ -906,11 +952,7 @@ uninstall_d2_standalone() {
return 1 return 1
fi fi
sh_c="sh_c" ensure_prefix_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" sh -c "'cd \"$INSTALL_DIR/d2-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'"
"$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION" "$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION"
} }
@ -928,11 +970,7 @@ uninstall_tala_standalone() {
return 1 return 1
fi fi
sh_c="sh_c" ensure_prefix_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" sh -c "'cd \"$INSTALL_DIR/tala-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'"
"$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION" "$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION"
} }
@ -941,13 +979,6 @@ uninstall_tala_brew() {
sh_c brew remove tala sh_c brew remove tala
} }
is_prefix_writable() {
# The reason for checking whether $INSTALL_DIR is writable 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.
is_writable_dir "$INSTALL_DIR"
}
cache_dir() { cache_dir() {
if [ -n "${XDG_CACHE_HOME-}" ]; then if [ -n "${XDG_CACHE_HOME-}" ]; then
echo "$XDG_CACHE_HOME/d2/release" echo "$XDG_CACHE_HOME/d2/release"