diff --git a/install-coder.sh b/install-coder.sh index 3eb9dc7..c0e888d 100644 --- a/install-coder.sh +++ b/install-coder.sh @@ -1,619 +1,871 @@ #!/usr/bin/env /bin/sh set -eu -# code-server's automatic install script. -# See https://coder.com/docs/code-server/latest/install +# Coder's automatic install script. +# See https://github.com/coder/coder#install +# +# To run: +# curl -L https://coder.com/install.sh | sh usage() { - arg0="$0" - if [ "$0" = sh ]; then - arg0="curl -fsSL https://code-server.dev/install.sh | sh -s --" - else - not_curl_usage="The latest script is available at https://code-server.dev/install.sh + arg0="$0" + if [ "$0" = sh ]; then + arg0="curl -fsSL https://coder.com/install.sh | sh -s --" + else + not_curl_usage="The latest script is available at https://coder.com/install.sh " - fi + fi - cath << EOF -Installs code-server. + cath < - Sets the prefix used by standalone release archives. Defaults to ~/.local - The release is unarchived into ~/.local/lib/code-server-X.X.X - and the binary symlinked into ~/.local/bin/code-server - To install system wide pass --prefix=/usr/local + Sets the prefix used by standalone release archives. Defaults to /usr/local + and the binary is copied into /usr/local/bin + To install in \$HOME, pass --prefix=\$HOME/.local + + --binary-name + Sets the name for the CLI in standalone release archives. Defaults to "coder" + To use the CLI as coder2, pass --binary-name=coder2 + Note: in-product documentation will always refer to the CLI as "coder" --rsh Specifies the remote shell for remote installation. Defaults to ssh. + --with-terraform + Installs Terraform binary from https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/ source + alongside coder. + This is great for if you are having issues with Coder installing terraform, or if you + just want it on your base system aswell. + This supports most systems, however if you are unsure yours is supported you can check + the link above. + --net-admin + Adds \`CAP_NET_ADMIN\` to the installed binary. This allows Coder to + increase network speeds, but has security implications. + See: https://man7.org/linux/man-pages/man7/capabilities.7.html + This only works on Linux based systems. + + The detection method works as follows: - Debian, Ubuntu, Raspbian: install the deb package from GitHub. - Fedora, CentOS, RHEL, openSUSE: install the rpm package from GitHub. - - Arch Linux: install from the AUR (which pulls releases from GitHub). - - FreeBSD, Alpine: install from npm. - - macOS: install using Homebrew if installed otherwise install from GitHub. - - All others: install the release from GitHub. + - Alpine: install the apk package from GitHub. + - macOS: if \`brew\` is available, install from the coder/coder Homebrew tap. + - Otherwise, download from GitHub and install into \`--prefix\`. -We only build releases on GitHub for amd64 and arm64 on Linux and amd64 for -macOS. When the detection method tries to pull a release from GitHub it will -fall back to installing from npm when there is no matching release for the -system's operating system and architecture. +We build releases on GitHub for amd64, armv7, and arm64 on Windows, Linux, and macOS. -The standalone method will force installion using GitHub releases. It will not -fall back to npm so on architectures without pre-built releases this will error. +When the detection method tries to pull a release from GitHub it will +fall back to installing standalone when there is no matching release for +the system's operating system and architecture. -The installer will cache all downloaded assets into ~/.cache/code-server - -More installation docs are at https://coder.com/docs/code-server/latest/install +The installer will cache all downloaded assets into ~/.cache/coder EOF } -echo_latest_version() { - if [ "${EDGE-}" ]; then - version="$(curl -fsSL https://api.github.com/repos/coder/code-server/releases | awk 'match($0,/.*"html_url": "(.*\/releases\/tag\/.*)".*/)' | head -n 1 | awk -F '"' '{print $4}')" - else - # https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c#gistcomment-2758860 - version="$(curl -fsSLI -o /dev/null -w "%{url_effective}" https://github.com/coder/code-server/releases/latest)" - fi - version="${version#https://github.com/coder/code-server/releases/tag/}" - version="${version#v}" - echo "$version" +echo_latest_stable_version() { + url="https://github.com/coder/coder/releases/latest" + # https://gist.github.com/lukechilds/a83e1d7127b78fef38c2914c4ececc3c#gistcomment-2758860 + response=$(curl -sSLI -o /dev/null -w "\n%{http_code} %{url_effective}" ${url}) + status_code=$(echo "$response" | tail -n1 | cut -d' ' -f1) + version=$(echo "$response" | tail -n1 | cut -d' ' -f2-) + body=$(echo "$response" | sed '$d') + + if [ "$status_code" != "200" ]; then + echoerr "GitHub API returned status code: ${status_code}" + echoerr "URL: ${url}" + exit 1 + fi + + version="${version#https://github.com/coder/coder/releases/tag/v}" + echo "${version}" } -echo_npm_postinstall() { - echoh - cath << EOF -npm package has been installed. +echo_latest_mainline_version() { + # Fetch the releases from the GitHub API, sort by version number, + # and take the first result. Note that we're sorting by space- + # separated numbers and without utilizing the sort -V flag for the + # best compatibility. + url="https://api.github.com/repos/coder/coder/releases" + response=$(curl -sSL -w "\n%{http_code}" ${url}) + status_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') -Extend your path to use code-server: - PATH="$NPM_BIN_DIR:\$PATH" -Then run with: - code-server -EOF + if [ "$status_code" != "200" ]; then + echoerr "GitHub API returned status code: ${status_code}" + echoerr "URL: ${url}" + echoerr "Response body: ${body}" + exit 1 + fi + + echo "$body" | + awk -F'"' '/"tag_name"/ {print $4}' | + tr -d v | + tr . ' ' | + sort -k1,1nr -k2,2nr -k3,3nr | + head -n1 | + tr ' ' . } echo_standalone_postinstall() { - echoh - cath << EOF -Standalone release has been installed into $STANDALONE_INSTALL_PREFIX/lib/code-server-$VERSION + if [ "${DRY_RUN-}" ]; then + echo_dryrun_postinstall + return + fi + + channel= + advisory="To install our stable release (v${STABLE_VERSION}), use the --stable flag. " + if [ "${STABLE}" = 1 ]; then + channel="stable " + advisory="" + fi + if [ "${MAINLINE}" = 1 ]; then + channel="mainline " + fi + + cath < + +EOF + fi } echo_brew_postinstall() { - echoh - cath << EOF -Brew release has been installed. + if [ "${DRY_RUN-}" ]; then + echo_dryrun_postinstall + return + fi + + BREW_PREFIX="$(brew --prefix)" + + cath < -Run with: - code-server EOF } echo_systemd_postinstall() { - echoh - cath << EOF + if [ "${DRY_RUN-}" ]; then + echo_dryrun_postinstall + return + fi + + echoh + cath < + EOF } -echo_coder_postinstall() { - echoh - echoh "Deploy code-server for your team with Coder: https://github.com/coder/coder" +echo_dryrun_postinstall() { + cath </dev/null || true + + sh_c="sh_c" + if [ ! -w "$TERRAFORM_INSTALL_PREFIX" ]; then + sh_c="sudo_sh_c" + fi + # Prepare /usr/local/bin/ and the binary for copying + "$sh_c" mkdir -p "$TERRAFORM_INSTALL_PREFIX/bin" + "$sh_c" unzip -d "$CACHE_DIR" -o "$CACHE_DIR/terraform_${TERRAFORM_VERSION}_${OS}_${ARCH}.zip" + COPY_LOCATION="$TERRAFORM_INSTALL_PREFIX/bin/terraform" + + # Remove the file if it already exists to + # avoid https://github.com/coder/coder/issues/2086 + if [ -f "$COPY_LOCATION" ]; then + "$sh_c" rm "$COPY_LOCATION" + fi + + # Copy the binary to the correct location. + "$sh_c" cp "$CACHE_DIR/terraform" "$COPY_LOCATION" +} + +install_macos() { + # If there is no `brew` binary available, just default to installing standalone + if command_exists brew; then + echoh "Installing coder with Homebrew from the coder/coder tap." + echoh + + sh_c brew install coder/coder/coder + + echo_brew_postinstall + return + fi + + echoh "Homebrew is not available." + echoh "Falling back to standalone installation." + install_standalone } install_deb() { - echoh "Installing v$VERSION of the $ARCH deb package from GitHub." - echoh + echoh "Installing v$VERSION of the $ARCH deb package from GitHub." + echoh - fetch "https://github.com/coder/code-server/releases/download/v$VERSION/code-server_${VERSION}_$ARCH.deb" \ - "$CACHE_DIR/code-server_${VERSION}_$ARCH.deb" - sudo_sh_c dpkg -i "$CACHE_DIR/code-server_${VERSION}_$ARCH.deb" + fetch "https://github.com/coder/coder/releases/download/v$VERSION/coder_${VERSION}_${OS}_${ARCH}.deb" \ + "$CACHE_DIR/coder_${VERSION}_$ARCH.deb" + sudo_sh_c dpkg --force-confdef --force-confold -i "$CACHE_DIR/coder_${VERSION}_$ARCH.deb" - echo_systemd_postinstall deb + echo_systemd_postinstall deb } install_rpm() { - echoh "Installing v$VERSION of the $ARCH rpm package from GitHub." - echoh + echoh "Installing v$VERSION of the $ARCH rpm package from GitHub." + echoh - fetch "https://github.com/coder/code-server/releases/download/v$VERSION/code-server-$VERSION-$ARCH.rpm" \ - "$CACHE_DIR/code-server-$VERSION-$ARCH.rpm" - sudo_sh_c rpm -U "$CACHE_DIR/code-server-$VERSION-$ARCH.rpm" + fetch "https://github.com/coder/coder/releases/download/v$VERSION/coder_${VERSION}_${OS}_${ARCH}.rpm" \ + "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.rpm" + sudo_sh_c rpm -U "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.rpm" - echo_systemd_postinstall rpm + echo_systemd_postinstall rpm } -install_aur() { - echoh "Installing latest from the AUR." - echoh +install_apk() { + echoh "Installing v$VERSION of the $ARCH apk package from GitHub." + echoh - sh_c mkdir -p "$CACHE_DIR/code-server-aur" - sh_c "curl -#fsSL https://aur.archlinux.org/cgit/aur.git/snapshot/code-server.tar.gz | tar -xzC $CACHE_DIR/code-server-aur --strip-components 1" - echo "+ cd $CACHE_DIR/code-server-aur" - if [ ! "${DRY_RUN-}" ]; then - cd "$CACHE_DIR/code-server-aur" - fi - sh_c makepkg -si --noconfirm + fetch "https://github.com/coder/coder/releases/download/v$VERSION/coder_${VERSION}_${OS}_${ARCH}.apk" \ + "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.apk" + sudo_sh_c apk add --allow-untrusted "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.apk" - echo_systemd_postinstall AUR + echo_systemd_postinstall apk } install_standalone() { - echoh "Installing v$VERSION of the $ARCH release from GitHub." - echoh + echoh "Installing v$VERSION of the $ARCH release from GitHub." + echoh - fetch "https://github.com/coder/code-server/releases/download/v$VERSION/code-server-$VERSION-$OS-$ARCH.tar.gz" \ - "$CACHE_DIR/code-server-$VERSION-$OS-$ARCH.tar.gz" + # macOS releases are packaged as .zip + case $OS in + darwin) STANDALONE_ARCHIVE_FORMAT=zip ;; + *) STANDALONE_ARCHIVE_FORMAT=tar.gz ;; + esac - # -w only works if the directory exists so try creating it first. If this - # fails we can ignore the error as the -w check will then swap us to sudo. - sh_c mkdir -p "$STANDALONE_INSTALL_PREFIX" 2> /dev/null || true + fetch "https://ghfast.top/https://github.com/coder/coder/releases/download/v$VERSION/coder_${VERSION}_${OS}_${ARCH}.$STANDALONE_ARCHIVE_FORMAT" \ + "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.$STANDALONE_ARCHIVE_FORMAT" - sh_c="sh_c" - if [ ! -w "$STANDALONE_INSTALL_PREFIX" ]; then - sh_c="sudo_sh_c" - fi + # -w only works if the directory exists so try creating it first. If this + # fails we can ignore the error as the -w check will then swap us to sudo. + sh_c mkdir -p "$STANDALONE_INSTALL_PREFIX" 2>/dev/null || true - if [ -e "$STANDALONE_INSTALL_PREFIX/lib/code-server-$VERSION" ]; then - echoh - echoh "code-server-$VERSION is already installed at $STANDALONE_INSTALL_PREFIX/lib/code-server-$VERSION" - echoh "Remove it to reinstall." - exit 0 - fi + sh_c mkdir -p "$CACHE_DIR/tmp" + if [ "$STANDALONE_ARCHIVE_FORMAT" = tar.gz ]; then + sh_c tar -C "$CACHE_DIR/tmp" -xzf "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.tar.gz" + else + sh_c unzip -d "$CACHE_DIR/tmp" -o "$CACHE_DIR/coder_${VERSION}_${OS}_${ARCH}.zip" + fi - "$sh_c" mkdir -p "$STANDALONE_INSTALL_PREFIX/lib" "$STANDALONE_INSTALL_PREFIX/bin" - "$sh_c" tar -C "$STANDALONE_INSTALL_PREFIX/lib" -xzf "$CACHE_DIR/code-server-$VERSION-$OS-$ARCH.tar.gz" - "$sh_c" mv -f "$STANDALONE_INSTALL_PREFIX/lib/code-server-$VERSION-$OS-$ARCH" "$STANDALONE_INSTALL_PREFIX/lib/code-server-$VERSION" - "$sh_c" ln -fs "$STANDALONE_INSTALL_PREFIX/lib/code-server-$VERSION/bin/code-server" "$STANDALONE_INSTALL_PREFIX/bin/code-server" + STANDALONE_BINARY_LOCATION="$STANDALONE_INSTALL_PREFIX/bin/$STANDALONE_BINARY_NAME" - echo_standalone_postinstall -} + sh_c="sh_c" + if [ ! -w "$STANDALONE_INSTALL_PREFIX" ]; then + sh_c="sudo_sh_c" + fi -install_npm() { - echoh "Installing latest from npm." - echoh + "$sh_c" mkdir -p "$STANDALONE_INSTALL_PREFIX/bin" - NPM_PATH="${YARN_PATH-npm}" + # Remove the file if it already exists to + # avoid https://github.com/coder/coder/issues/2086 + if [ -f "$STANDALONE_BINARY_LOCATION" ]; then + "$sh_c" rm "$STANDALONE_BINARY_LOCATION" + fi - if command_exists "$NPM_PATH"; then - sh_c="sh_c" - if [ ! "${DRY_RUN-}" ] && [ ! -w "$(NPM_PATH config get prefix)" ]; then - sh_c="sudo_sh_c" - fi - echoh "Installing with npm." - echoh - "$sh_c" "$NPM_PATH" install -g code-server --unsafe-perm - NPM_BIN_DIR="\$($NPM_PATH bin -g)" echo_npm_postinstall - return - fi - echoerr "Please install npm to install code-server!" - echoerr "You will need at least node v18 and a few C dependencies." - echoerr "See the docs https://coder.com/docs/code-server/latest/install#npm" + # Copy the binary to the correct location. + "$sh_c" cp "$CACHE_DIR/tmp/coder" "$STANDALONE_BINARY_LOCATION" - exit 1 -} + # Clean up the extracted files (note, not using sudo: $sh_c -> sh_c). + sh_c rm -rv "$CACHE_DIR/tmp" -# Run $1 if we have a standalone otherwise run install_npm. -npm_fallback() { - if has_standalone; then - $1 - else - echoh "No standalone releases for $ARCH." - echoh "Falling back to installation from npm." - install_npm - fi + echo_standalone_postinstall } # Determine if we have standalone releases on GitHub for the system's arch. has_standalone() { - case $ARCH in - amd64) return 0 ;; - # We only have amd64 for macOS. - arm64) - [ "$(distro)" != macos ] - return - ;; - *) return 1 ;; - esac + case $ARCH in + amd64) return 0 ;; + arm64) return 0 ;; + armv7) + [ "$(distro)" != darwin ] + return + ;; + *) return 1 ;; + esac } os() { - uname="$(uname)" - case $uname in - Linux) echo linux ;; - Darwin) echo macos ;; - FreeBSD) echo freebsd ;; - *) echo "$uname" ;; - esac + uname="$(uname)" + case $uname in + Linux) echo linux ;; + Darwin) echo darwin ;; + FreeBSD) echo freebsd ;; + *) echo "$uname" ;; + esac } # Print the detected Linux distro, otherwise print the OS name. # # Example outputs: -# - macos -> macos +# - darwin -> darwin # - freebsd -> freebsd # - ubuntu, raspbian, debian ... -> debian # - amzn, centos, rhel, fedora, ... -> fedora # - opensuse-{leap,tumbleweed} -> opensuse # - alpine -> alpine -# - arch, manjaro, endeavouros, ... -> arch +# - arch -> arch # # Inspired by https://github.com/docker/docker-install/blob/26ff363bcf3b3f5a00498ac43694bf1c7d9ce16c/install.sh#L111-L120. distro() { - if [ "$OS" = "macos" ] || [ "$OS" = "freebsd" ]; then - echo "$OS" - return - fi + if [ "$OS" = "darwin" ] || [ "$OS" = "freebsd" ]; then + echo "$OS" + return + fi - if [ -f /etc/os-release ]; then - ( - . /etc/os-release - if [ "${ID_LIKE-}" ]; then - for id_like in $ID_LIKE; do - case "$id_like" in debian | fedora | opensuse | arch) - echo "$id_like" - return - ;; - esac - done - fi + if [ -f /etc/os-release ]; then + ( + # shellcheck disable=SC1091 + . /etc/os-release + if [ "${ID_LIKE-}" ]; then + for id_like in $ID_LIKE; do + case "$id_like" in debian | fedora | opensuse) + echo "$id_like" + return + ;; + esac + done + fi - echo "$ID" - ) - return - fi + echo "$ID" + ) + return + fi } # Print a human-readable name for the OS/distro. distro_name() { - if [ "$(uname)" = "Darwin" ]; then - echo "macOS v$(sw_vers -productVersion)" - return - fi + if [ "$(uname)" = "Darwin" ]; then + echo "macOS v$(sw_vers -productVersion)" + return + fi - if [ -f /etc/os-release ]; then - ( - . /etc/os-release - echo "$PRETTY_NAME" - ) - return - fi + if [ -f /etc/os-release ]; then + ( + # shellcheck disable=SC1091 + . /etc/os-release + echo "$PRETTY_NAME" + ) + return + fi - # Prints something like: Linux 4.19.0-9-amd64 - uname -sr + # Prints something like: Linux 4.19.0-9-amd64 + uname -sr } arch() { - uname_m=$(uname -m) - case $uname_m in - aarch64) echo arm64 ;; - x86_64) echo amd64 ;; - *) echo "$uname_m" ;; - esac + uname_m=$(uname -m) + case $uname_m in + aarch64) echo arm64 ;; + x86_64) echo amd64 ;; + armv7l) echo armv7 ;; + *) echo "$uname_m" ;; + esac +} + +# The following is to change the naming, that way people with armv7 won't receive a error +# List of binaries can be found here: https://releases.hashicorp.com/terraform/ +terraform_arch() { + uname_m=$(uname -m) + case $uname_m in + aarch64) echo arm64 ;; + x86_64) echo amd64 ;; + armv7l) echo arm ;; + *) echo "$uname_m" ;; + esac } command_exists() { - if [ ! "$1" ]; then return 1; fi - command -v "$@" > /dev/null + if [ ! "$1" ]; then return 1; fi + command -v "$@" >/dev/null } sh_c() { - echoh "+ $*" - if [ ! "${DRY_RUN-}" ]; then - sh -c "HTTPS_PROXY=http://vaala:bcali@100.100.0.9:8899 HTTP_PROXY=http://vaala:bcali@100.100.0.9:8899 $*" - fi + echoh "+ $*" + if [ ! "${DRY_RUN-}" ]; then + sh -c "$*" + fi } sudo_sh_c() { - if [ "$(id -u)" = 0 ]; then - sh_c "$@" - elif command_exists doas; then - sh_c "doas $*" - elif command_exists sudo; then - sh_c "sudo $*" - elif command_exists su; then - sh_c "su root -c '$*'" - else - echoh - echoerr "This script needs to run the following command as root." - echoerr " $*" - echoerr "Please install doas, sudo, or su." - exit 1 - fi + if [ "$(id -u)" = 0 ]; then + sh_c "$@" + elif command_exists sudo; then + sh_c "sudo $*" + elif command_exists doas; then + sh_c "doas $*" + elif command_exists su; then + sh_c "su - -c '$*'" + else + echoh + echoerr "This script needs to run the following command as root." + echoerr " $*" + echoerr "Please install sudo, su, or doas." + exit 1 + fi } echo_cache_dir() { - if [ "${XDG_CACHE_HOME-}" ]; then - echo "$XDG_CACHE_HOME/code-server" - elif [ "${HOME-}" ]; then - echo "$HOME/.cache/code-server" - else - echo "/tmp/code-server-cache" - fi + if [ "${XDG_CACHE_HOME-}" ]; then + echo "$XDG_CACHE_HOME/coder" + elif [ "${HOME-}" ]; then + echo "$HOME/.cache/coder" + else + echo "/tmp/coder-cache" + fi } echoh() { - echo "$@" | humanpath + echo "$@" | humanpath } cath() { - humanpath + humanpath } echoerr() { - echoh "$@" >&2 + echoh "$@" >&2 } # humanpath replaces all occurrences of " $HOME" with " ~" # and all occurrences of '"$HOME' with the literal '"$HOME'. humanpath() { - sed "s# $HOME# ~#g; s#\"$HOME#\"\$HOME#g" + sed "s# $HOME# ~#g; s#\"$HOME#\"\$HOME#g" } # We need to make sure we exit with a non zero exit if the command fails. # /bin/sh does not support -o pipefail unfortunately. prefix() { - PREFIX="$1" - shift - fifo="$(mktemp -d)/fifo" - mkfifo "$fifo" - sed -e "s#^#$PREFIX: #" "$fifo" & - "$@" > "$fifo" 2>&1 + PREFIX="$1" + shift + fifo="$(mktemp -d)/fifo" + mkfifo "$fifo" + sed -e "s#^#$PREFIX: #" "$fifo" & + "$@" >"$fifo" 2>&1 } main "$@" -