diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 5ec3ae5..52cb471 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -44,13 +44,6 @@ jobs: - latest - 20.04 - 18.04 - include: - - TAG: 18.04 - PLATFORMS: linux/amd64 - exclude: - # no arm bins for docker CLIs - - TAG: 18.04 - PLATFORMS: linux/amd64,linux/arm64,linux/armhf steps: - name: Login to GitHub Container Registry if: ${{ github.repository_owner == github.actor }} diff --git a/linux/ubuntu/scripts/act.sh b/linux/ubuntu/scripts/act.sh index 2fb7327..e66e8f8 100755 --- a/linux/ubuntu/scripts/act.sh +++ b/linux/ubuntu/scripts/act.sh @@ -1,6 +1,4 @@ #!/bin/bash - -# disable warning about 'mkdir -m -p' # shellcheck disable=SC2174 set -Eeuxo pipefail @@ -10,12 +8,23 @@ printf "\n\tšŸ‹ Build started šŸ‹\t\n" # Remove '"' so it can be sourced by sh/bash sed 's|"||g' -i "/etc/environment" -ImageOS=ubuntu$(echo "${FROM_TAG}" | cut -d'.' -f 1) +. /etc/os-release + +node_arch() { + case "$(uname -m)" in + 'aarch64') echo 'arm64' ;; + 'x86_64') echo 'x64' ;; + 'armv7l') echo 'armv7l' ;; + *) exit 1 ;; + esac +} + +ImageOS=ubuntu$(echo "${VERSION_ID}" | cut -d'.' -f 1) AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache { echo "IMAGE_OS=$ImageOS" echo "ImageOS=$ImageOS" - echo "LSB_RELEASE=${FROM_TAG}" + echo "LSB_RELEASE=${VERSION_ID}" echo "AGENT_TOOLSDIRECTORY=${AGENT_TOOLSDIRECTORY}" echo "RUN_TOOL_CACHE=${AGENT_TOOLSDIRECTORY}" echo "DEPLOYMENT_BASEPATH=/opt/runner" @@ -32,10 +41,8 @@ chown -R 1001:1000 /github printf "\n\tšŸ‹ Installing packages šŸ‹\t\n" packages=( ssh - lsb-release gawk curl - git jq wget sudo @@ -55,21 +62,28 @@ apt-get -yq install --no-install-recommends --no-install-suggests "${packages[@] ln -s "$(which python3)" "/usr/local/bin/python" -LSB_OS_VERSION=$(lsb_release -rs | sed 's|\.||g') +add-apt-repository ppa:git-core/ppa -y +apt-get update +apt-get install -y git + +git --version + +wget https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh -qO- | bash +apt-get update +apt-get install -y git-lfs + +LSB_OS_VERSION="${VERSION_ID//\./}" echo "LSB_OS_VERSION=${LSB_OS_VERSION}" | tee -a "/etc/environment" wget -qO "/imagegeneration/toolset.json" "https://raw.githubusercontent.com/actions/virtual-environments/main/images/linux/toolsets/toolset-${LSB_OS_VERSION}.json" wget -qO "/imagegeneration/LICENSE" "https://raw.githubusercontent.com/actions/virtual-environments/main/LICENSE" -ARCH=$(uname -m) -if [ "$ARCH" = x86_64 ]; then - ARCH=x64 +if [ "$(uname -m)" = x86_64 ]; then wget -qO "/usr/bin/jq" "https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64" chmod +x "/usr/bin/jq" fi -if [ "$ARCH" = aarch64 ]; then ARCH=arm64; fi -if [[ "${FROM_TAG}" == "16.04" ]]; then +if [[ "${VERSION_ID}" == "16.04" ]]; then printf 'git-lfs not available for Xenial' else apt-get -yq install --no-install-recommends --no-install-suggests git-lfs @@ -87,7 +101,11 @@ mkdir -m 0700 -p ~/.ssh printf "\n\tšŸ‹ Installed base utils šŸ‹\t\n" printf "\n\tšŸ‹ Installing docker cli šŸ‹\t\n" -curl "https://packages.microsoft.com/config/ubuntu/${FROM_TAG}/prod.list" | tee /etc/apt/sources.list.d/microsoft-prod.list +if [[ "${VERSION_ID}" == "18.04" ]]; then + echo "deb https://packages.microsoft.com/ubuntu/${VERSION_ID}/multiarch/prod ${VERSION_CODENAME} main" | tee /etc/apt/sources.list.d/microsoft-prod.list +else + echo "deb https://packages.microsoft.com/ubuntu/${VERSION_ID}/prod ${VERSION_CODENAME} main" | tee /etc/apt/sources.list.d/microsoft-prod.list +fi wget -q https://packages.microsoft.com/keys/microsoft.asc gpg --dearmor /etc/apt/trusted.gpg.d/microsoft.gpg apt-key add - >/etc/environment + # ENVVAR="${V//\./_}" + # echo "${ENVVAR}=${GOPATH}" >>/etc/environment printf "\n\tšŸ‹ Installed GO šŸ‹\t\n" "$GOPATH/bin/go" version diff --git a/linux/ubuntu/scripts/helpers/etc-environment.sh b/linux/ubuntu/scripts/helpers/etc-environment.sh new file mode 100755 index 0000000..b3bb50a --- /dev/null +++ b/linux/ubuntu/scripts/helpers/etc-environment.sh @@ -0,0 +1,85 @@ +#!/bin/bash -e +################################################################################ +## File: etc-environment.sh +## Desc: Helper functions for source and modify /etc/environment +################################################################################ + +# NB: sed expression use '%' as a delimiter in order to simplify handling +# values containing slashes (i.e. directory path) +# The values containing '%' will break the functions + +function getEtcEnvironmentVariable { + variable_name="$1" + # remove `variable_name=` and possible quotes from the line + grep "^${variable_name}=" /etc/environment |sed -E "s%^${variable_name}=\"?([^\"]+)\"?.*$%\1%" +} + +function addEtcEnvironmentVariable { + variable_name="$1" + variable_value="$2" + + echo "$variable_name=$variable_value" | sudo tee -a /etc/environment +} + +function replaceEtcEnvironmentVariable { + variable_name="$1" + variable_value="$2" + + # modify /etc/environemnt in place by replacing a string that begins with variable_name + sudo sed -i -e "s%^${variable_name}=.*$%${variable_name}=\"${variable_value}\"%" /etc/environment +} + +function setEtcEnvironmentVariable { + variable_name="$1" + variable_value="$2" + + if grep "$variable_name" /etc/environment > /dev/null; then + replaceEtcEnvironmentVariable "$variable_name" "$variable_value" + else + addEtcEnvironmentVariable "$variable_name" "$variable_value" + fi +} + +function prependEtcEnvironmentVariable { + variable_name="$1" + element="$2" + # TODO: handle the case if the variable does not exist + existing_value=$(getEtcEnvironmentVariable "${variable_name}") + setEtcEnvironmentVariable "${variable_name}" "${element}:${existing_value}" +} + +function appendEtcEnvironmentVariable { + variable_name="$1" + element="$2" + # TODO: handle the case if the variable does not exist + existing_value=$(getEtcEnvironmentVariable "${variable_name}") + setEtcEnvironmentVariable "${variable_name}" "${existing_value}:${element}" +} + +function prependEtcEnvironmentPath { + element="$1" + prependEtcEnvironmentVariable PATH "${element}" +} + +function appendEtcEnvironmentPath { + element="$1" + appendEtcEnvironmentVariable PATH "${element}" +} + +# Process /etc/environment as if it were shell script with `export VAR=...` expressions +# The PATH variable is handled specially in order to do not override the existing PATH +# variable. The value of PATH variable read from /etc/environment is added to the end +# of value of the exiting PATH variable exactly as it would happen with real PAM app read +# /etc/environment +# +# TODO: there might be the others variables to be processed in the same way as "PATH" variable +# ie MANPATH, INFOPATH, LD_*, etc. In the current implementation the values from /etc/evironments +# replace the values of the current environment +function reloadEtcEnvironment { + # add `export ` to every variable of /etc/environemnt except PATH and eval the result shell script + eval "$(grep -v '^PATH=' /etc/environment | sed -e 's%^%export %')" + # handle PATH specially + etc_path=$(getEtcEnvironmentVariable PATH) + export PATH="$PATH:$etc_path" +} + diff --git a/linux/ubuntu/scripts/helpers/os.sh b/linux/ubuntu/scripts/helpers/os.sh index f7be471..0ad0137 100755 --- a/linux/ubuntu/scripts/helpers/os.sh +++ b/linux/ubuntu/scripts/helpers/os.sh @@ -5,13 +5,16 @@ ################################################################################ function isUbuntu18() { - lsb_release -d | grep -q 'Ubuntu 18' + . /etc/os-release + [[ "${VERSION_ID}" =~ ^18\.(.*)$ ]] } function isUbuntu20() { - lsb_release -d | grep -q 'Ubuntu 20' + . /etc/os-release + [[ "${VERSION_ID}" =~ ^20\.(.*)$ ]] } function getOSVersionLabel() { - lsb_release -cs + . /etc/os-release + echo "${VERSION_CODENAME}" } diff --git a/linux/ubuntu/scripts/java-tools.sh b/linux/ubuntu/scripts/java-tools.sh index 82425b7..7eac3bf 100755 --- a/linux/ubuntu/scripts/java-tools.sh +++ b/linux/ubuntu/scripts/java-tools.sh @@ -1,4 +1,5 @@ #!/bin/bash -e +# shellcheck disable=SC1091,SC2002,2207 ################################################################################ ## File: java-tools.sh ## Desc: Installs Java and related tooling (Ant, Gradle, Maven) @@ -6,64 +7,132 @@ set -Eeuxo pipefail -# shellcheck disable=SC1091 + . /etc/environment -# shellcheck disable=SC1091 . /imagegeneration/installers/helpers/os.sh -# shellcheck disable=SC1091 . /imagegeneration/installers/helpers/install.sh +. /imagegeneration/installers/helpers/etc-environment.sh -JAVA_VERSIONS_LIST=$(get_toolset_value '.java.versions | .[]') -DEFAULT_JDK_VERSION=$(get_toolset_value '.java.default') -JAVA_TOOLCACHE_PATH="$AGENT_TOOLSDIRECTORY/Java_Adopt_jdk" +createJavaEnvironmentalVariable() { + local JAVA_VERSION=$1 + local VENDOR_NAME=$2 + local DEFAULT=$3 -# Install GPG Key for Adopt Open JDK. See https://adoptopenjdk.net/installation.html -wget -qO - "https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public" | apt-key add - -add-apt-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/ + case "${VENDOR_NAME}" in -if isUbuntu18; then - # Install GPG Key for Azul Open JDK. See https://www.azul.com/downloads/azure-only/zulu/ - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0xB1998361219BD9C9 - apt-add-repository "deb https://repos.azul.com/azure-only/zulu/apt stable main" -fi + "Adopt" ) + INSTALL_PATH_PATTERN="/usr/lib/jvm/adoptopenjdk-${JAVA_VERSION}-hotspot-amd64" ;; + "Temurin-Hotspot" ) + INSTALL_PATH_PATTERN="/usr/lib/jvm/temurin-${JAVA_VERSION}-jdk-amd64" ;; + *) + echo "Unknown vendor" + exit 1 + + esac + + if [[ "${DEFAULT}" == "True" ]]; then + echo "Setting up JAVA_HOME variable to ${INSTALL_PATH_PATTERN}" + addEtcEnvironmentVariable JAVA_HOME "${INSTALL_PATH_PATTERN}" + echo "Setting up default symlink" + update-java-alternatives -s "${INSTALL_PATH_PATTERN}" + fi + + echo "Setting up JAVA_HOME_${JAVA_VERSION}_X64 variable to ${INSTALL_PATH_PATTERN}" + addEtcEnvironmentVariable JAVA_HOME_"${JAVA_VERSION}"_X64 "${INSTALL_PATH_PATTERN}" +} + +enableRepositories() { + # Add Adopt PPA + wget -qO - "https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public" | apt-key add - + add-apt-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/ + + # Add Addoptium PPA + wget -qO - "https://packages.adoptium.net/artifactory/api/gpg/key/public" | apt-key add - + add-apt-repository --yes https://packages.adoptium.net/artifactory/deb/ + + if isUbuntu18 ; then + # Install GPG Key for Azul Open JDK. See https://www.azul.com/downloads/azure-only/zulu/ + apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0xB1998361219BD9C9 + apt-add-repository "deb https://repos.azul.com/azure-only/zulu/apt stable main" + fi +} + +installOpenJDK() { + local JAVA_VERSION=$1 + local VENDOR_NAME=$2 + + # Install Java from PPA repositories. + if [[ "${VENDOR_NAME}" == "Temurin-Hotspot" ]]; then + apt-get -y install temurin-"${JAVA_VERSION}"-jdk=\* + javaVersionPath="/usr/lib/jvm/temurin-${JAVA_VERSION}-jdk-amd64" + elif [[ "${VENDOR_NAME}" == "Adopt" ]]; then + apt-get -y install adoptopenjdk-"${JAVA_VERSION}"-hotspot=\* + javaVersionPath="/usr/lib/jvm/adoptopenjdk-${JAVA_VERSION}-hotspot-amd64" + else + echo "${VENDOR_NAME} is invalid, valid names are: Temurin-Hotspot and Adopt" + exit 1 + fi + + JAVA_TOOLCACHE_PATH="${AGENT_TOOLSDIRECTORY}/Java_${VENDOR_NAME}_jdk" + + fullJavaVersion=$(cat "${javaVersionPath}"/release | grep "^SEMANTIC" | cut -d "=" -f 2 | tr -d "\"" | tr "+" "-") + + # If there is no semver in java release, then extract java version from -fullversion + [[ -z "${fullJavaVersion}" ]] && fullJavaVersion=$("${javaVersionPath}"/bin/java -fullversion 2>&1 | tr -d "\"" | tr "+" "-" | awk '{print $4}') + + javaToolcacheVersionPath="${JAVA_TOOLCACHE_PATH}/${fullJavaVersion}" + mkdir -p "${javaToolcacheVersionPath}" + + # Create a complete file + touch "${javaToolcacheVersionPath}/x64.complete" + + # Create symlink for Java + ln -s "${javaVersionPath}" "${javaToolcacheVersionPath}"/x64 + + # add extra permissions to be able execute command without sudo + chmod -R 777 /usr/lib/jvm +} + +# Fetch repositories data +enableRepositories + +# Get all the updates from enabled repositories. apt-get update -# shellcheck disable=SC2068 -for JAVA_VERSION in ${JAVA_VERSIONS_LIST[@]}; do - apt-get -y install adoptopenjdk-"$JAVA_VERSION"-hotspot=\* - javaVersionPath="/usr/lib/jvm/adoptopenjdk-${JAVA_VERSION}-hotspot-amd64" - echo "JAVA_HOME_${JAVA_VERSION}_X64=$javaVersionPath" | tee -a /etc/environment - fullJavaVersion=$(grep "^SEMANTIC" <"$javaVersionPath/release" | cut -d "=" -f 2 | tr -d "\"" | tr "+" "-") +defaultVersion=$(jq '.java.default' '/imagegeneration/toolset.json') +defaultVendor=$(jq '.java.default_vendor' '/imagegeneration/toolset.json') +jdkVendors=($(jq '.java.vendors[].name' '/imagegeneration/toolset.json')) - # If there is no semver in java release, then extract java version from -fullversion - if [[ -z $fullJavaVersion ]]; then - fullJavaVersion=$(java -fullversion 2>&1 | tr -d "\"" | tr "+" "-" | awk '{print $4}') - fi +for jdkVendor in "${jdkVendors[@]}"; do - javaToolcacheVersionPath="$JAVA_TOOLCACHE_PATH/$fullJavaVersion" - mkdir -p "$javaToolcacheVersionPath" + # get vendor-specific versions + jdkVersionsToInstall=($(jq ".java.vendors[] | select (.name==\"${jdkVendor}\") | .versions[]" "/imagegeneration/toolset.json")) - # Create a complete file - touch "$javaToolcacheVersionPath/x64.complete" + for jdkVersionToInstall in "${jdkVersionsToInstall[@]}"; do - # Create symlink for Java - ln -s "$javaVersionPath" "$javaToolcacheVersionPath/x64" + installOpenJDK "${jdkVersionToInstall}" "${jdkVendor}" + + isDefaultVersion=False; [[ "${jdkVersionToInstall}" == "${defaultVersion}" ]] && isDefaultVersion=True + + if [[ "${jdkVendor}" == "${defaultVendor}" ]]; then + createJavaEnvironmentalVariable "${jdkVersionToInstall}" "${jdkVendor}" "${isDefaultVersion}" + fi + + done done -# Set Default Java version -update-java-alternatives -s /usr/lib/jvm/adoptopenjdk-"${DEFAULT_JDK_VERSION}"-hotspot-amd64 +# Adopt 12 is only available for Ubuntu 18.04 +if isUbuntu18; then + createJavaEnvironmentalVariable "12" "Adopt" +fi -echo "JAVA_HOME=/usr/lib/jvm/adoptopenjdk-${DEFAULT_JDK_VERSION}-hotspot-amd64" | tee -a /etc/environment - -# add extra permissions to be able execute command without sudo -chmod -R 777 /usr/lib/jvm # Install Ant -apt-fast install -y --no-install-recommends ant ant-optional +apt-get install -y --no-install-recommends ant ant-optional echo "ANT_HOME=/usr/share/ant" | tee -a /etc/environment # Install Maven -mavenVersion=$(get_toolset_value '.java.maven') +mavenVersion=$(jq '.java.maven' "/imagegeneration/toolset.json") mavenDownloadUrl="https://www-eu.apache.org/dist/maven/maven-3/${mavenVersion}/binaries/apache-maven-${mavenVersion}-bin.zip" download_with_retries "${mavenDownloadUrl}" "/tmp" "maven.zip" unzip -qq -d /usr/share /tmp/maven.zip @@ -73,11 +142,13 @@ ln -s /usr/share/apache-maven-"${mavenVersion}"/bin/mvn /usr/bin/mvn # This script founds the latest gradle release from https://services.gradle.org/versions/all # The release is downloaded, extracted, a symlink is created that points to it, and GRADLE_HOME is set. gradleJson=$(curl -s https://services.gradle.org/versions/all) -gradleLatestVersion=$(echo "$gradleJson" | jq -r '.[] | select(.version | contains("-") | not).version' | sort -V | tail -n1) -gradleDownloadUrl=$(echo "$gradleJson" | jq -r ".[] | select(.version==\"$gradleLatestVersion\") | .downloadUrl") -echo "gradleUrl=$gradleDownloadUrl" -echo "gradleVersion=$gradleLatestVersion" -download_with_retries "$gradleDownloadUrl" "/tmp" "gradleLatest.zip" +gradleLatestVersion=$(echo "${gradleJson}" | jq -r '.[] | select(.version | contains("-") | not).version' | sort -V | tail -n1) +gradleDownloadUrl=$(echo "${gradleJson}" | jq -r ".[] | select(.version==\"$gradleLatestVersion\") | .downloadUrl") +echo "gradleUrl=${gradleDownloadUrl}" +echo "gradleVersion=${gradleLatestVersion}" +download_with_retries "${gradleDownloadUrl}" "/tmp" "gradleLatest.zip" unzip -qq -d /usr/share /tmp/gradleLatest.zip ln -s /usr/share/gradle-"${gradleLatestVersion}"/bin/gradle /usr/bin/gradle echo "GRADLE_HOME=$(find /usr/share -depth -maxdepth 1 -name "gradle*")" | tee -a /etc/environment + +reloadEtcEnvironment diff --git a/linux/ubuntu/scripts/js.sh b/linux/ubuntu/scripts/js.sh index 0605ad3..7e8ebcb 100755 --- a/linux/ubuntu/scripts/js.sh +++ b/linux/ubuntu/scripts/js.sh @@ -1,8 +1,8 @@ #!/bin/bash +# shellcheck disable=SC1091,SC2174,SC2016 set -Eeuo pipefail -# shellcheck disable=SC1091 . /etc/environment printf "\n\tšŸ‹ Installing NVM tools šŸ‹\t\n" @@ -15,8 +15,6 @@ echo "NVM_DIR=$HOME/.nvm" | tee -a /etc/environment # shellcheck disable=SC2016 echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' | tee -a /etc/skel/.bash_profile -# Not following: ./nvm.sh was not specified as input (see shellcheck -x).shellcheck(SC1091) -# shellcheck disable=SC1091 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" printf "\n\tšŸ‹ Installed NVM šŸ‹\t\n" @@ -31,16 +29,14 @@ for V in "${versions[@]}"; do VER=$(echo "${JSON}" | jq "[.[] | select(.version|test(\"^v${V}\"))][0].version" -r) NODEPATH="$AGENT_TOOLSDIRECTORY/node/${VER:1}/x64" - # disable warning about 'mkdir -m -p' - # shellcheck disable=SC2174 mkdir -v -m 0777 -p "$NODEPATH" ARCH=$(uname -m) if [ "$ARCH" = x86_64 ]; then ARCH=x64; fi if [ "$ARCH" = aarch64 ]; then ARCH=arm64; fi wget -qO- "https://nodejs.org/download/release/latest-v${V}.x/node-$VER-linux-$ARCH.tar.xz" | tar -Jxf - --strip-components=1 -C "$NODEPATH" - ENVVAR="${V//\./_}" - echo "${ENVVAR}=${NODEPATH}" >>/etc/environment + # ENVVAR="${V//\./_}" + # echo "${ENVVAR}=${NODEPATH}" >>/etc/environment printf "\n\tšŸ‹ Installed NODE šŸ‹\t\n" "$NODEPATH/bin/node" -v diff --git a/linux/ubuntu/scripts/pwsh.sh b/linux/ubuntu/scripts/pwsh.sh index ce23fd5..51796c6 100755 --- a/linux/ubuntu/scripts/pwsh.sh +++ b/linux/ubuntu/scripts/pwsh.sh @@ -1,8 +1,8 @@ #!/bin/bash +# shellcheck disable=SC1091 set -Eeuxo pipefail -# shellcheck disable=SC1091 . /etc/environment printf "\n\tšŸ‹ Installing PowerShell šŸ‹\t\n" diff --git a/linux/ubuntu/scripts/runner.sh b/linux/ubuntu/scripts/runner.sh index a764be6..7aefb5f 100755 --- a/linux/ubuntu/scripts/runner.sh +++ b/linux/ubuntu/scripts/runner.sh @@ -1,7 +1,5 @@ #!/bin/bash - -# disable warning about 'mkdir -m -p' -# shellcheck disable=SC2174 +# shellcheck disable=SC2174,SC1091 set -Eeuxo pipefail @@ -43,7 +41,6 @@ mkdir -m 0700 -p "/home/${RUNNER}/.ssh" chmod 644 "/home/${RUNNER}/.ssh/known_hosts" chown -R "${RUNNER}":"${RUNNER}" "/home/${RUNNER}/.ssh" -# shellcheck disable=SC1091 . /etc/environment # Word is of the form "A"B"C" (B indicated). Did you mean "ABC" or "A\"B\"C"?shellcheck(SC2140) diff --git a/linux/ubuntu/scripts/rust.sh b/linux/ubuntu/scripts/rust.sh index e3b7ed8..41dfbcf 100755 --- a/linux/ubuntu/scripts/rust.sh +++ b/linux/ubuntu/scripts/rust.sh @@ -1,8 +1,8 @@ #!/bin/bash +# shellcheck disable=SC1091 set -Eeuxo pipefail -# shellcheck disable=SC1091 . /etc/environment export RUSTUP_HOME=/usr/share/rust/.rustup @@ -15,7 +15,6 @@ apt-get -yq install build-essential llvm printf "\n\tšŸ‹ Installing Rust šŸ‹\t\n" curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain=stable --profile=minimal -# shellcheck disable=SC1091 source "${CARGO_HOME}/env" rustup component add rustfmt clippy