aboutsummaryrefslogtreecommitdiff
path: root/collection
diff options
context:
space:
mode:
Diffstat (limited to 'collection')
-rw-r--r--collection/anaconda/feature.json16
-rw-r--r--collection/anaconda/install.sh99
-rw-r--r--collection/aws-cli/feature.json21
-rw-r--r--collection/aws-cli/install.sh142
-rw-r--r--collection/azure-cli/feature.json17
-rw-r--r--collection/azure-cli/install.sh186
-rw-r--r--collection/common/feature.json52
-rw-r--r--collection/common/install.sh454
-rw-r--r--collection/desktop-lite/feature.json39
-rw-r--r--collection/desktop-lite/install.sh394
-rw-r--r--collection/docker-from-docker/feature.json41
-rw-r--r--collection/docker-from-docker/install.sh354
-rw-r--r--collection/docker-in-docker/feature.json42
-rw-r--r--collection/docker-in-docker/install.sh397
-rw-r--r--collection/dotnet/feature.json28
-rw-r--r--collection/dotnet/install.sh374
-rw-r--r--collection/git-lfs/feature.json16
-rw-r--r--collection/git-lfs/install.sh194
-rw-r--r--collection/git/feature.json21
-rw-r--r--collection/git/install.sh153
-rw-r--r--collection/github-cli/feature.json16
-rw-r--r--collection/github-cli/install.sh188
-rw-r--r--collection/go/feature.json24
-rw-r--r--collection/go/install.sh255
-rw-r--r--collection/gradle/feature.json22
-rw-r--r--collection/gradle/install.sh0
-rw-r--r--collection/hugo/feature.json16
-rw-r--r--collection/hugo/install.sh113
-rw-r--r--collection/java/feature.json26
-rw-r--r--collection/java/install.sh145
-rw-r--r--collection/jekyll/feature.json16
-rw-r--r--collection/jekyll/install.sh60
-rw-r--r--collection/jupyterlab/feature.json21
-rw-r--r--collection/jupyterlab/install.sh51
-rw-r--r--collection/kubectl-helm-minikube/feature.json38
-rw-r--r--collection/kubectl-helm-minikube/install.sh249
-rw-r--r--collection/maven/feature.json22
-rw-r--r--collection/maven/install.sh133
-rw-r--r--collection/node/feature.json27
-rw-r--r--collection/node/install.sh169
-rw-r--r--collection/powershell/feature.json16
-rw-r--r--collection/powershell/install.sh170
-rw-r--r--collection/python/feature.json56
-rw-r--r--collection/python/python-debian.sh376
-rw-r--r--collection/ruby/feature.json25
-rw-r--r--collection/ruby/install.sh271
-rw-r--r--collection/rust/feature.json43
-rw-r--r--collection/rust/install.sh199
-rw-r--r--collection/sshd/feature.json17
-rw-r--r--collection/sshd/install.sh169
-rw-r--r--collection/terraform/feature.json39
-rw-r--r--collection/terraform/install.sh218
52 files changed, 6230 insertions, 0 deletions
diff --git a/collection/anaconda/feature.json b/collection/anaconda/feature.json
new file mode 100644
index 0000000..7af90cd
--- /dev/null
+++ b/collection/anaconda/feature.json
@@ -0,0 +1,16 @@
+{
+ "id": "anaconda",
+ "name": "Anaconda",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Select or enter an anaconda version."
+ }
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/anaconda/install.sh b/collection/anaconda/install.sh
new file mode 100644
index 0000000..c77d98d
--- /dev/null
+++ b/collection/anaconda/install.sh
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/anaconda.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./anaconda-debian.sh [Conda version] [CONDA_DIR] [Non-root user] [Add rc files flag]
+
+VERSION=${1:-"latest"}
+export CONDA_DIR=${2:-"/usr/local/conda"}
+USERNAME=${3:-"automatic"}
+UPDATE_RC=${4:-"true"}
+
+set -eux
+export DEBIAN_FRONTEND=noninteractive
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+architecture="$(uname -m)"
+if [ "${architecture}" != "x86_64" ]; then
+ echo "(!) Architecture $architecture unsupported"
+ exit 1
+fi
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt-get update
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Install Conda if it's missing
+if ! conda --version &> /dev/null ; then
+ # Install dependencies
+ check_packages wget ca-certificates
+
+ mkdir -p $CONDA_DIR
+ chown ${USERNAME}:root $CONDA_DIR
+ echo "Installing Anaconda..."
+
+ CONDA_VERSION=$VERSION
+ if [ "${VERSION}" = "latest" ] || [ "${VERSION}" = "lts" ]; then
+ CONDA_VERSION="2021.11"
+ fi
+
+ su --login -c "wget -q https://repo.anaconda.com/archive/Anaconda3-${CONDA_VERSION}-Linux-x86_64.sh -O /tmp/anaconda-install.sh \
+ && /bin/bash /tmp/anaconda-install.sh -u -b -p ${CONDA_DIR}" ${USERNAME} 2>&1
+
+ if [ "${VERSION}" = "latest" ] || [ "${VERSION}" = "lts" ]; then
+ PATH=$PATH:${CONDA_DIR}/bin
+ conda update -y conda
+ fi
+
+ rm /tmp/anaconda-install.sh
+ updaterc "export CONDA_DIR=${CONDA_DIR}/bin"
+fi
+
+echo "Done!" \ No newline at end of file
diff --git a/collection/aws-cli/feature.json b/collection/aws-cli/feature.json
new file mode 100644
index 0000000..ee1df10
--- /dev/null
+++ b/collection/aws-cli/feature.json
@@ -0,0 +1,21 @@
+{
+ "id": "aws-cli",
+ "name": "AWS CLI",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": [
+ "latest"
+ ],
+ "default": "latest",
+ "description": "Select or enter an AWS CLI version. (Available versions here: https://github.com/aws/aws-cli/blob/v2/CHANGELOG.rst)"
+ }
+ },
+ "extensions": [
+ "AmazonWebServices.aws-toolkit-vscode"
+ ],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/aws-cli/install.sh b/collection/aws-cli/install.sh
new file mode 100644
index 0000000..0d204de
--- /dev/null
+++ b/collection/aws-cli/install.sh
@@ -0,0 +1,142 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/awscli.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./awscli-debian.sh [AWS CLI version]
+
+set -e
+
+AWSCLI_VERSION=${1:-"latest"}
+AWSCLI_GPG_KEY=FB5DB77FD5C118B80511ADA8A6310ACC4672475C
+AWSCLI_GPG_KEY_MATERIAL="-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBF2Cr7UBEADJZHcgusOJl7ENSyumXh85z0TRV0xJorM2B/JL0kHOyigQluUG
+ZMLhENaG0bYatdrKP+3H91lvK050pXwnO/R7fB/FSTouki4ciIx5OuLlnJZIxSzx
+PqGl0mkxImLNbGWoi6Lto0LYxqHN2iQtzlwTVmq9733zd3XfcXrZ3+LblHAgEt5G
+TfNxEKJ8soPLyWmwDH6HWCnjZ/aIQRBTIQ05uVeEoYxSh6wOai7ss/KveoSNBbYz
+gbdzoqI2Y8cgH2nbfgp3DSasaLZEdCSsIsK1u05CinE7k2qZ7KgKAUIcT/cR/grk
+C6VwsnDU0OUCideXcQ8WeHutqvgZH1JgKDbznoIzeQHJD238GEu+eKhRHcz8/jeG
+94zkcgJOz3KbZGYMiTh277Fvj9zzvZsbMBCedV1BTg3TqgvdX4bdkhf5cH+7NtWO
+lrFj6UwAsGukBTAOxC0l/dnSmZhJ7Z1KmEWilro/gOrjtOxqRQutlIqG22TaqoPG
+fYVN+en3Zwbt97kcgZDwqbuykNt64oZWc4XKCa3mprEGC3IbJTBFqglXmZ7l9ywG
+EEUJYOlb2XrSuPWml39beWdKM8kzr1OjnlOm6+lpTRCBfo0wa9F8YZRhHPAkwKkX
+XDeOGpWRj4ohOx0d2GWkyV5xyN14p2tQOCdOODmz80yUTgRpPVQUtOEhXQARAQAB
+tCFBV1MgQ0xJIFRlYW0gPGF3cy1jbGlAYW1hem9uLmNvbT6JAlQEEwEIAD4WIQT7
+Xbd/1cEYuAURraimMQrMRnJHXAUCXYKvtQIbAwUJB4TOAAULCQgHAgYVCgkICwIE
+FgIDAQIeAQIXgAAKCRCmMQrMRnJHXJIXEAChLUIkg80uPUkGjE3jejvQSA1aWuAM
+yzy6fdpdlRUz6M6nmsUhOExjVIvibEJpzK5mhuSZ4lb0vJ2ZUPgCv4zs2nBd7BGJ
+MxKiWgBReGvTdqZ0SzyYH4PYCJSE732x/Fw9hfnh1dMTXNcrQXzwOmmFNNegG0Ox
+au+VnpcR5Kz3smiTrIwZbRudo1ijhCYPQ7t5CMp9kjC6bObvy1hSIg2xNbMAN/Do
+ikebAl36uA6Y/Uczjj3GxZW4ZWeFirMidKbtqvUz2y0UFszobjiBSqZZHCreC34B
+hw9bFNpuWC/0SrXgohdsc6vK50pDGdV5kM2qo9tMQ/izsAwTh/d/GzZv8H4lV9eO
+tEis+EpR497PaxKKh9tJf0N6Q1YLRHof5xePZtOIlS3gfvsH5hXA3HJ9yIxb8T0H
+QYmVr3aIUes20i6meI3fuV36VFupwfrTKaL7VXnsrK2fq5cRvyJLNzXucg0WAjPF
+RrAGLzY7nP1xeg1a0aeP+pdsqjqlPJom8OCWc1+6DWbg0jsC74WoesAqgBItODMB
+rsal1y/q+bPzpsnWjzHV8+1/EtZmSc8ZUGSJOPkfC7hObnfkl18h+1QtKTjZme4d
+H17gsBJr+opwJw/Zio2LMjQBOqlm3K1A4zFTh7wBC7He6KPQea1p2XAMgtvATtNe
+YLZATHZKTJyiqA==
+=vYOk
+-----END PGP PUBLIC KEY BLOCK-----"
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+check_packages curl ca-certificates gnupg2 dirmngr unzip
+
+verify_aws_cli_gpg_signature() {
+ local filePath=$1
+ local sigFilePath=$2
+
+ get_common_setting AWSCLI_GPG_KEY
+ get_common_setting AWSCLI_GPG_KEY_MATERIAL true
+ local awsGpgKeyring=aws-cli-public-key.gpg
+
+ echo "${AWSCLI_GPG_KEY_MATERIAL}" | gpg --dearmor > "./${awsGpgKeyring}"
+ gpg --batch --quiet --no-default-keyring --keyring "./${awsGpgKeyring}" --verify "${sigFilePath}" "${filePath}"
+ local status=$?
+
+ rm "./${awsGpgKeyring}"
+
+ return ${status}
+}
+
+install() {
+ local scriptZipFile=awscli.zip
+ local scriptSigFile=awscli.sig
+
+ # See Linux install docs at https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
+ if [ "${AWSCLI_VERSION}" != "latest" ]; then
+ local versionStr=-${AWSCLI_VERSION}
+ fi
+ architecture=$(dpkg --print-architecture)
+ case "${architecture}" in
+ amd64) architectureStr=x86_64 ;;
+ arm64) architectureStr=aarch64 ;;
+ *)
+ echo "AWS CLI does not support machine architecture '$architecture'. Please use an x86-64 or ARM64 machine."
+ exit 1
+ esac
+ local scriptUrl=https://awscli.amazonaws.com/awscli-exe-linux-${architectureStr}${versionStr}.zip
+ curl "${scriptUrl}" -o "${scriptZipFile}"
+ curl "${scriptUrl}.sig" -o "${scriptSigFile}"
+
+ verify_aws_cli_gpg_signature "$scriptZipFile" "$scriptSigFile"
+ if (( $? > 0 )); then
+ echo "Could not verify GPG signature of AWS CLI install script. Make sure you provided a valid version."
+ exit 1
+ fi
+
+ unzip "${scriptZipFile}"
+ ./aws/install
+
+ rm -rf ./aws
+}
+
+echo "(*) Installing AWS CLI..."
+
+install
+
+echo "Done!"
diff --git a/collection/azure-cli/feature.json b/collection/azure-cli/feature.json
new file mode 100644
index 0000000..6ae3769
--- /dev/null
+++ b/collection/azure-cli/feature.json
@@ -0,0 +1,17 @@
+{
+ "id": "azure-cli",
+ "name": "Azure CLI",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Select or enter an Azure CLI version. (Available versions may vary by Linux distribution.)"
+ }
+ },
+ "extensions": ["ms-vscode.azurecli"],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+}
diff --git a/collection/azure-cli/install.sh b/collection/azure-cli/install.sh
new file mode 100644
index 0000000..a7f2a84
--- /dev/null
+++ b/collection/azure-cli/install.sh
@@ -0,0 +1,186 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/azcli.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./azcli-debian.sh
+
+set -e
+
+AZ_VERSION=${1:-"latest"}
+MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
+AZCLI_ARCHIVE_ARCHITECTURES="amd64"
+AZCLI_ARCHIVE_VERSION_CODENAMES="stretch buster bullseye bionic focal"
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Soft version matching that resolves a version for a given package in the *current apt-cache*
+# Return value is stored in first argument (the unprocessed version)
+apt_cache_version_soft_match() {
+
+ # Version
+ local variable_name="$1"
+ local requested_version=${!variable_name}
+ # Package Name
+ local package_name="$2"
+ # Exit on no match?
+ local exit_on_no_match="${3:-true}"
+
+ # Ensure we've exported useful variables
+ . /etc/os-release
+ local architecture="$(dpkg --print-architecture)"
+
+ dot_escaped="${requested_version//./\\.}"
+ dot_plus_escaped="${dot_escaped//+/\\+}"
+ # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/
+ version_regex="^(.+:)?${dot_plus_escaped}([\\.\\+ ~:-]|$)"
+ set +e # Don't exit if finding version fails - handle gracefully
+ fuzzy_version="$(apt-cache madison ${package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${version_regex}")"
+ set -e
+ if [ -z "${fuzzy_version}" ]; then
+ echo "(!) No full or partial for package \"${package_name}\" match found in apt-cache for \"${requested_version}\" on OS ${ID} ${VERSION_CODENAME} (${architecture})."
+
+ if $exit_on_no_match; then
+ echo "Available versions:"
+ apt-cache madison ${package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+'
+ exit 1 # Fail entire script
+ else
+ echo "Continuing to fallback method (if available)"
+ return 1;
+ fi
+ fi
+
+ # Globally assign fuzzy_version to this value
+ # Use this value as the return value of this function
+ declare -g ${variable_name}="=${fuzzy_version}"
+ echo "${variable_name} ${!variable_name}"
+}
+
+install_using_apt() {
+ # Install dependencies
+ check_packages apt-transport-https curl ca-certificates gnupg2 dirmngr
+ # Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install
+ get_common_setting MICROSOFT_GPG_KEYS_URI
+ curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
+ echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/azure-cli/ ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/azure-cli.list
+ apt-get update
+
+ if [ "${AZ_VERSION}" = "latest" ] || [ "${AZ_VERSION}" = "lts" ] || [ "${AZ_VERSION}" = "stable" ]; then
+ # Empty, meaning grab the "latest" in the apt repo
+ AZ_VERSION=""
+ else
+ # Sets AZ_VERSION to our desired version, if match found.
+ apt_cache_version_soft_match AZ_VERSION "azure-cli" false
+ if [ "$?" != 0 ]; then
+ return 1
+ fi
+ fi
+
+ if ! (apt-get install -yq azure-cli${AZ_VERSION}); then
+ rm -f /etc/apt/sources.list.d/azure-cli.list
+ return 1
+ fi
+}
+
+install_using_pip() {
+ echo "(*) No pre-built binaries available in apt-cache. Installing via pip3."
+ if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install python3-minimal python3-pip libffi-dev python3-venv
+ fi
+ export PIPX_HOME=/usr/local/pipx
+ mkdir -p ${PIPX_HOME}
+ export PIPX_BIN_DIR=/usr/local/bin
+ export PYTHONUSERBASE=/tmp/pip-tmp
+ export PIP_CACHE_DIR=/tmp/pip-tmp/cache
+ pipx_bin=pipx
+ if ! type pipx > /dev/null 2>&1; then
+ pip3 install --disable-pip-version-check --no-cache-dir --user pipx
+ pipx_bin=/tmp/pip-tmp/bin/pipx
+ fi
+
+ if [ "${AZ_VERSION}" = "latest" ] || [ "${AZ_VERSION}" = "lts" ] || [ "${AZ_VERSION}" = "stable" ]; then
+ # Empty, meaning grab the "latest" in the apt repo
+ ver=""
+ else
+ ver="==${AZ_VERSION}"
+ fi
+
+ set +e
+ ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' -f azure-cli${ver}
+
+ # Fail gracefully
+ if [ "$?" != 0 ]; then
+ echo "Could not install azure-cli${ver} via pip"
+ rm -rf /tmp/pip-tmp
+ return 1
+ fi
+ set -e
+}
+
+# See if we're on x86_64 and if so, install via apt-get, otherwise use pip3
+echo "(*) Installing Azure CLI..."
+. /etc/os-release
+architecture="$(dpkg --print-architecture)"
+if [[ "${AZCLI_ARCHIVE_ARCHITECTURES}" = *"${architecture}"* ]] && [[ "${AZCLI_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]]; then
+ install_using_apt || use_pip="true"
+else
+ use_pip="true"
+fi
+
+if [ "${use_pip}" = "true" ]; then
+ install_using_pip
+
+ if [ "$?" != 0 ]; then
+ echo "Please provide a valid version for your distribution ${ID} ${VERSION_CODENAME} (${architecture})."
+ echo
+ echo "Valid versions in current apt-cache"
+ apt-cache madison azure-cli | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+'
+ exit 1
+ fi
+fi
+
+echo "Done!" \ No newline at end of file
diff --git a/collection/common/feature.json b/collection/common/feature.json
new file mode 100644
index 0000000..d6b94f2
--- /dev/null
+++ b/collection/common/feature.json
@@ -0,0 +1,52 @@
+{
+ "id": "common",
+ "name": "common",
+ "description": "common",
+ "options": {
+ "installZsh": {
+ "type": "boolean",
+ "default": true,
+ "description": "Install ZSH?"
+ },
+ "installOhMyZsh": {
+ "type": "boolean",
+ "default": true,
+ "description": "Install Oh My Zsh!?"
+ },
+ "upgradePackages": {
+ "type":"boolean",
+ "default": true,
+ "description": "Upgrade OS packages?"
+ },
+ "username": {
+ "type":"string",
+ "proposals": ["vscode", "codespace", "none", "automatic"],
+ "default": "automatic",
+ "description": "Enter name of non-root user to configure or none to skip"
+ },
+ "uid": {
+ "type":"string",
+ "proposals": ["1000", "automatic"],
+ "default": "automatic",
+ "description": "Enter uid for non-root user"
+ },
+ "gid": {
+ "type": "string",
+ "proposals": ["1000", "automatic"],
+ "default": "automatic",
+ "description": "Enter gid for non-root user"
+ },
+ "nonFreePackages": {
+ "type":"boolean",
+ "default": true,
+ "description": "Add packages from non-free Debian repository?"
+ }
+ },
+ "extensions": [
+ "ms-dotnettools.csharp"
+ ],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/common/install.sh b/collection/common/install.sh
new file mode 100644
index 0000000..af4facc
--- /dev/null
+++ b/collection/common/install.sh
@@ -0,0 +1,454 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages]
+
+set -e
+
+INSTALL_ZSH=${1:-"true"}
+USERNAME=${2:-"automatic"}
+USER_UID=${3:-"automatic"}
+USER_GID=${4:-"automatic"}
+UPGRADE_PACKAGES=${5:-"true"}
+INSTALL_OH_MYS=${6:-"true"}
+ADD_NON_FREE_PACKAGES=${7:-"false"}
+SCRIPT_DIR="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)"
+MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# If in automatic mode, determine if a user already exists, if not use vscode
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=vscode
+ fi
+elif [ "${USERNAME}" = "none" ]; then
+ USERNAME=root
+ USER_UID=0
+ USER_GID=0
+fi
+
+# Load markers to see which steps have already run
+if [ -f "${MARKER_FILE}" ]; then
+ echo "Marker file found:"
+ cat "${MARKER_FILE}"
+ source "${MARKER_FILE}"
+fi
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+# Function to call apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies
+if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
+
+ package_list="apt-utils \
+ openssh-client \
+ gnupg2 \
+ dirmngr \
+ iproute2 \
+ procps \
+ lsof \
+ htop \
+ net-tools \
+ psmisc \
+ curl \
+ wget \
+ rsync \
+ ca-certificates \
+ unzip \
+ zip \
+ nano \
+ vim-tiny \
+ less \
+ jq \
+ lsb-release \
+ apt-transport-https \
+ dialog \
+ libc6 \
+ libgcc1 \
+ libkrb5-3 \
+ libgssapi-krb5-2 \
+ libicu[0-9][0-9] \
+ liblttng-ust0 \
+ libstdc++6 \
+ zlib1g \
+ locales \
+ sudo \
+ ncdu \
+ man-db \
+ strace \
+ manpages \
+ manpages-dev \
+ init-system-helpers"
+
+ # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian
+ if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then
+ # Bring in variables from /etc/os-release like VERSION_CODENAME
+ . /etc/os-release
+ sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list
+ sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list
+ sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list
+ sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list
+ sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list
+ sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list
+ sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list
+ sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list
+ # Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html
+ sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list
+ sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list
+ echo "Running apt-get update..."
+ apt-get update
+ package_list="${package_list} manpages-posix manpages-posix-dev"
+ else
+ apt_get_update_if_needed
+ fi
+
+ # Install libssl1.1 if available
+ if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then
+ package_list="${package_list} libssl1.1"
+ fi
+
+ # Install appropriate version of libssl1.0.x if available
+ libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
+ if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then
+ if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then
+ # Debian 9
+ package_list="${package_list} libssl1.0.2"
+ elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then
+ # Ubuntu 18.04, 16.04, earlier
+ package_list="${package_list} libssl1.0.0"
+ fi
+ fi
+
+ echo "Packages to verify are installed: ${package_list}"
+ apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
+
+ # Install git if not already installed (may be more recent than distro version)
+ if ! type git > /dev/null 2>&1; then
+ apt-get -y install --no-install-recommends git
+ fi
+
+ PACKAGES_ALREADY_INSTALLED="true"
+fi
+
+# Get to latest versions of all packages
+if [ "${UPGRADE_PACKAGES}" = "true" ]; then
+ apt_get_update_if_needed
+ apt-get -y upgrade --no-install-recommends
+ apt-get autoremove -y
+fi
+
+# Ensure at least the en_US.UTF-8 UTF-8 locale is available.
+# Common need for both applications and things like the agnoster ZSH theme.
+if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then
+ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
+ locale-gen
+ LOCALE_ALREADY_SET="true"
+fi
+
+# Create or update a non-root user to match UID/GID.
+group_name="${USERNAME}"
+if id -u ${USERNAME} > /dev/null 2>&1; then
+ # User exists, update if needed
+ if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then
+ group_name="$(id -gn $USERNAME)"
+ groupmod --gid $USER_GID ${group_name}
+ usermod --gid $USER_GID $USERNAME
+ fi
+ if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then
+ usermod --uid $USER_UID $USERNAME
+ fi
+else
+ # Create user
+ if [ "${USER_GID}" = "automatic" ]; then
+ groupadd $USERNAME
+ else
+ groupadd --gid $USER_GID $USERNAME
+ fi
+ if [ "${USER_UID}" = "automatic" ]; then
+ useradd -s /bin/bash --gid $USERNAME -m $USERNAME
+ else
+ useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME
+ fi
+fi
+
+# Add add sudo support for non-root user
+if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then
+ echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME
+ chmod 0440 /etc/sudoers.d/$USERNAME
+ EXISTING_NON_ROOT_USER="${USERNAME}"
+fi
+
+# ** Shell customization section **
+if [ "${USERNAME}" = "root" ]; then
+ user_rc_path="/root"
+else
+ user_rc_path="/home/${USERNAME}"
+fi
+
+# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty
+if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then
+ cp /etc/skel/.bashrc "${user_rc_path}/.bashrc"
+fi
+
+# Restore user .profile defaults from skeleton file if it doesn't exist or is empty
+if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then
+ cp /etc/skel/.profile "${user_rc_path}/.profile"
+fi
+
+# .bashrc/.zshrc snippet
+rc_snippet="$(cat << 'EOF'
+
+if [ -z "${USER}" ]; then export USER=$(whoami); fi
+if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi
+
+# Display optional first run image specific notice if configured and terminal is interactive
+if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then
+ if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then
+ cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt"
+ elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then
+ cat "/workspaces/.codespaces/shared/first-run-notice.txt"
+ fi
+ mkdir -p "$HOME/.config/vscode-dev-containers"
+ # Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it
+ ((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &)
+fi
+
+# Set the default git editor if not already set
+if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then
+ if [ "${TERM_PROGRAM}" = "vscode" ]; then
+ if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then
+ export GIT_EDITOR="code-insiders --wait"
+ else
+ export GIT_EDITOR="code --wait"
+ fi
+ fi
+fi
+
+EOF
+)"
+
+# code shim, it fallbacks to code-insiders if code is not available
+cat << 'EOF' > /usr/local/bin/code
+#!/bin/sh
+
+get_in_path_except_current() {
+ which -a "$1" | grep -A1 "$0" | grep -v "$0"
+}
+
+code="$(get_in_path_except_current code)"
+
+if [ -n "$code" ]; then
+ exec "$code" "$@"
+elif [ "$(command -v code-insiders)" ]; then
+ exec code-insiders "$@"
+else
+ echo "code or code-insiders is not installed" >&2
+ exit 127
+fi
+EOF
+chmod +x /usr/local/bin/code
+
+# systemctl shim - tells people to use 'service' if systemd is not running
+cat << 'EOF' > /usr/local/bin/systemctl
+#!/bin/sh
+set -e
+if [ -d "/run/systemd/system" ]; then
+ exec /bin/systemctl/systemctl "$@"
+else
+ echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services instead. e.g.: \n\nservice --status-all'
+fi
+EOF
+chmod +x /usr/local/bin/systemctl
+
+# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme
+codespaces_bash="$(cat \
+<<'EOF'
+
+# Codespaces bash prompt theme
+__bash_prompt() {
+ local userpart='`export XIT=$? \
+ && [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \
+ && [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`'
+ local gitbranch='`\
+ if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \
+ export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \
+ if [ "${BRANCH}" != "" ]; then \
+ echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \
+ && if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \
+ echo -n " \[\033[1;33m\]✗"; \
+ fi \
+ && echo -n "\[\033[0;36m\]) "; \
+ fi; \
+ fi`'
+ local lightblue='\[\033[1;34m\]'
+ local removecolor='\[\033[0m\]'
+ PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ "
+ unset -f __bash_prompt
+}
+__bash_prompt
+
+EOF
+)"
+
+codespaces_zsh="$(cat \
+<<'EOF'
+# Codespaces zsh prompt theme
+__zsh_prompt() {
+ local prompt_username
+ if [ ! -z "${GITHUB_USER}" ]; then
+ prompt_username="@${GITHUB_USER}"
+ else
+ prompt_username="%n"
+ fi
+ PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow
+ PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd
+ PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status
+ PROMPT+='%{$fg[white]%}$ %{$reset_color%}'
+ unset -f __zsh_prompt
+}
+ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}"
+ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} "
+ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})"
+ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})"
+__zsh_prompt
+
+EOF
+)"
+
+# Add RC snippet and custom bash prompt
+if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then
+ echo "${rc_snippet}" >> /etc/bash.bashrc
+ echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc"
+ echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc"
+ if [ "${USERNAME}" != "root" ]; then
+ echo "${codespaces_bash}" >> "/root/.bashrc"
+ echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc"
+ fi
+ chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc"
+ RC_SNIPPET_ALREADY_ADDED="true"
+fi
+
+# Optionally install and configure zsh and Oh My Zsh!
+if [ "${INSTALL_ZSH}" = "true" ]; then
+ if ! type zsh > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get install -y zsh
+ fi
+ if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
+ echo "${rc_snippet}" >> /etc/zsh/zshrc
+ ZSH_ALREADY_INSTALLED="true"
+ fi
+
+ # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme.
+ # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script.
+ oh_my_install_dir="${user_rc_path}/.oh-my-zsh"
+ if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then
+ template_path="${oh_my_install_dir}/templates/zshrc.zsh-template"
+ user_rc_file="${user_rc_path}/.zshrc"
+ umask g-w,o-w
+ mkdir -p ${oh_my_install_dir}
+ git clone --depth=1 \
+ -c core.eol=lf \
+ -c core.autocrlf=false \
+ -c fsck.zeroPaddedFilemode=ignore \
+ -c fetch.fsck.zeroPaddedFilemode=ignore \
+ -c receive.fsck.zeroPaddedFilemode=ignore \
+ "https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1
+ echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file}
+ sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file}
+
+ mkdir -p ${oh_my_install_dir}/custom/themes
+ echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme"
+ # Shrink git while still enabling updates
+ cd "${oh_my_install_dir}"
+ git repack -a -d -f --depth=1 --window=1
+ # Copy to non-root user if one is specified
+ if [ "${USERNAME}" != "root" ]; then
+ cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root
+ chown -R ${USERNAME}:${group_name} "${user_rc_path}"
+ fi
+ fi
+fi
+
+# Persist image metadata info, script if meta.env found in same directory
+meta_info_script="$(cat << 'EOF'
+#!/bin/sh
+. /usr/local/etc/vscode-dev-containers/meta.env
+
+# Minimal output
+if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then
+ echo "${VERSION}"
+ exit 0
+elif [ "$1" = "release" ]; then
+ echo "${GIT_REPOSITORY_RELEASE}"
+ exit 0
+elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then
+ echo "${CONTENTS_URL}"
+ exit 0
+fi
+
+#Full output
+echo
+echo "Development container image information"
+echo
+if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi
+if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi
+if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi
+if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi
+if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi
+if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi
+if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi
+echo
+EOF
+)"
+if [ -f "${SCRIPT_DIR}/meta.env" ]; then
+ mkdir -p /usr/local/etc/vscode-dev-containers/
+ cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env
+ echo "${meta_info_script}" > /usr/local/bin/devcontainer-info
+ chmod +x /usr/local/bin/devcontainer-info
+fi
+
+# Write marker file
+mkdir -p "$(dirname "${MARKER_FILE}")"
+echo -e "\
+ PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\
+ LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\
+ EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\
+ RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\
+ ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}"
+
+echo "Done!"
diff --git a/collection/desktop-lite/feature.json b/collection/desktop-lite/feature.json
new file mode 100644
index 0000000..4ad8b8d
--- /dev/null
+++ b/collection/desktop-lite/feature.json
@@ -0,0 +1,39 @@
+{
+ "id": "desktop-lite",
+ "name": "Light-weight desktop (Fluxbox)",
+ "options": {
+ "version": {
+ "type": "string",
+ "enum": ["latest"],
+ "default": "latest",
+ "description": "Currently unused."
+ },
+ "password": {
+ "type": "string",
+ "proposals": ["vscode","codespaces","password"],
+ "default": "vscode",
+ "description": "Enter a password for desktop connections"
+ },
+ "webPort": {
+ "type": "string",
+ "proposals": ["6080"],
+ "default": "6080",
+ "description": "Enter a port for the desktop web client"
+ },
+ "vncPort": {
+ "type": "string",
+ "proposals": ["5901"],
+ "default": "5901",
+ "description": "Enter a port for the desktop VNC server"
+ }
+ },
+ "init": true,
+ "entrypoint": "/usr/local/share/desktop-init.sh",
+ "containerEnv": {
+ "DISPLAY": ":1"
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/desktop-lite/install.sh b/collection/desktop-lite/install.sh
new file mode 100644
index 0000000..aeddac3
--- /dev/null
+++ b/collection/desktop-lite/install.sh
@@ -0,0 +1,394 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/desktop-lite.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./desktop-lite-debian.sh [non-root user] [Desktop password] [Install web client flag] [VNC port] [Web Port]
+
+USERNAME=${1:-"automatic"}
+VNC_PASSWORD=${2:-"vscode"}
+INSTALL_NOVNC=${3:-"true"}
+VNC_PORT="${4:-5901}"
+NOVNC_PORT="${5:-6080}"
+
+NOVNC_VERSION=1.2.0
+WEBSOCKETIFY_VERSION=0.10.0
+
+package_list="
+ tigervnc-standalone-server \
+ tigervnc-common \
+ fluxbox \
+ dbus-x11 \
+ x11-utils \
+ x11-xserver-utils \
+ xdg-utils \
+ fbautostart \
+ at-spi2-core \
+ xterm \
+ eterm \
+ nautilus\
+ mousepad \
+ seahorse \
+ gnome-icon-theme \
+ gnome-keyring \
+ libx11-dev \
+ libxkbfile-dev \
+ libsecret-1-dev \
+ libgbm-dev \
+ libnotify4 \
+ libnss3 \
+ libxss1 \
+ libasound2 \
+ xfonts-base \
+ xfonts-terminus \
+ fonts-noto \
+ fonts-wqy-microhei \
+ fonts-droid-fallback \
+ htop \
+ ncdu \
+ curl \
+ ca-certificates\
+ unzip \
+ nano \
+ locales"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+# Add default Fluxbox config files if none are already present
+fluxbox_apps="$(cat \
+<< 'EOF'
+[transient] (role=GtkFileChooserDialog)
+ [Dimensions] {70% 70%}
+ [Position] (CENTER) {0 0}
+[end]
+EOF
+)"
+
+fluxbox_init="$(cat \
+<< 'EOF'
+session.configVersion: 13
+session.menuFile: ~/.fluxbox/menu
+session.keyFile: ~/.fluxbox/keys
+session.styleFile: /usr/share/fluxbox/styles/qnx-photon
+session.screen0.workspaces: 1
+session.screen0.workspacewarping: false
+session.screen0.toolbar.widthPercent: 100
+session.screen0.strftimeFormat: %a %l:%M %p
+session.screen0.toolbar.tools: RootMenu, clock, iconbar, systemtray
+session.screen0.workspaceNames: One,
+EOF
+)"
+
+fluxbox_menu="$(cat \
+<< 'EOF'
+[begin] ( Application Menu )
+ [exec] (File Manager) { nautilus ~ } <>
+ [exec] (Text Editor) { mousepad } <>
+ [exec] (Terminal) { tilix -w ~ -e $(readlink -f /proc/$$/exe) -il } <>
+ [exec] (Web Browser) { x-www-browser --disable-dev-shm-usage } <>
+ [submenu] (System) {}
+ [exec] (Set Resolution) { tilix -t "Set Resolution" -e bash /usr/local/bin/set-resolution } <>
+ [exec] (Edit Application Menu) { mousepad ~/.fluxbox/menu } <>
+ [exec] (Passwords and Keys) { seahorse } <>
+ [exec] (Top Processes) { tilix -t "Top" -e htop } <>
+ [exec] (Disk Utilization) { tilix -t "Disk Utilization" -e ncdu / } <>
+ [exec] (Editres) {editres} <>
+ [exec] (Xfontsel) {xfontsel} <>
+ [exec] (Xkill) {xkill} <>
+ [exec] (Xrefresh) {xrefresh} <>
+ [end]
+ [config] (Configuration)
+ [workspaces] (Workspaces)
+[end]
+EOF
+)"
+
+# Copy config files if the don't already exist
+copy_fluxbox_config() {
+ local target_dir="$1"
+ mkdir -p "${target_dir}/.fluxbox"
+ touch "${target_dir}/.Xmodmap"
+ if [ ! -e "${target_dir}/.fluxbox/apps" ]; then
+ echo "${fluxbox_apps}" > "${target_dir}/.fluxbox/apps"
+ fi
+ if [ ! -e "${target_dir}/.fluxbox/init" ]; then
+ echo "${fluxbox_init}" > "${target_dir}/.fluxbox/init"
+ fi
+ if [ ! -e "${target_dir}/.fluxbox/menu" ]; then
+ echo "${fluxbox_menu}" > "${target_dir}/.fluxbox/menu"
+ fi
+}
+
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+apt_get_update_if_needed
+
+# On older Ubuntu, Tilix is in a PPA. on Debian strech its in backports.
+if [[ -z $(apt-cache --names-only search ^tilix$) ]]; then
+ . /etc/os-release
+ if [ "${ID}" = "ubuntu" ]; then
+ apt-get install -y --no-install-recommends apt-transport-https software-properties-common
+ add-apt-repository -y ppa:webupd8team/terminix
+ elif [ "${VERSION_CODENAME}" = "stretch" ]; then
+ echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/stretch-backports.list
+ fi
+ apt-get update
+ if [[ -z $(apt-cache --names-only search ^tilix$) ]]; then
+ echo "(!) WARNING: Tilix not available on ${ID} ${VERSION_CODENAME} architecture $(uname -m). Skipping."
+ else
+ package_list="${package_list} tilix"
+ fi
+else
+ package_list="${package_list} tilix"
+fi
+
+# Install X11, fluxbox and VS Code dependencies
+check_packages ${package_list}
+
+# Install Emoji font if available in distro - Available in Debian 10+, Ubuntu 18.04+
+if dpkg-query -W fonts-noto-color-emoji > /dev/null 2>&1 && ! dpkg -s fonts-noto-color-emoji > /dev/null 2>&1; then
+ apt-get -y install --no-install-recommends fonts-noto-color-emoji
+fi
+
+# Check at least one locale exists
+if ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then
+ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
+ locale-gen
+fi
+
+# Install the Cascadia Code fonts - https://github.com/microsoft/cascadia-code
+if [ ! -d "/usr/share/fonts/truetype/cascadia" ]; then
+ curl -sSL https://github.com/microsoft/cascadia-code/releases/download/v2008.25/CascadiaCode-2008.25.zip -o /tmp/cascadia-fonts.zip
+ unzip /tmp/cascadia-fonts.zip -d /tmp/cascadia-fonts
+ mkdir -p /usr/share/fonts/truetype/cascadia
+ mv /tmp/cascadia-fonts/ttf/* /usr/share/fonts/truetype/cascadia/
+ rm -rf /tmp/cascadia-fonts.zip /tmp/cascadia-fonts
+fi
+
+# Install noVNC
+if [ "${INSTALL_NOVNC}" = "true" ] && [ ! -d "/usr/local/novnc" ]; then
+ mkdir -p /usr/local/novnc
+ curl -sSL https://github.com/novnc/noVNC/archive/v${NOVNC_VERSION}.zip -o /tmp/novnc-install.zip
+ unzip /tmp/novnc-install.zip -d /usr/local/novnc
+ cp /usr/local/novnc/noVNC-${NOVNC_VERSION}/vnc.html /usr/local/novnc/noVNC-${NOVNC_VERSION}/index.html
+ curl -sSL https://github.com/novnc/websockify/archive/v${WEBSOCKETIFY_VERSION}.zip -o /tmp/websockify-install.zip
+ unzip /tmp/websockify-install.zip -d /usr/local/novnc
+ ln -s /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION} /usr/local/novnc/noVNC-${NOVNC_VERSION}/utils/websockify
+ rm -f /tmp/websockify-install.zip /tmp/novnc-install.zip
+
+ # Install noVNC dependencies and use them.
+ if ! dpkg -s python3-minimal python3-numpy > /dev/null 2>&1; then
+ apt-get -y install --no-install-recommends python3-minimal python3-numpy
+ fi
+ sed -i -E 's/^python /python3 /' /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION}/run
+fi
+
+# Set up folders for scripts and init files
+mkdir -p /var/run/dbus /usr/local/etc/vscode-dev-containers/
+
+# Script to change resolution of desktop
+cat << EOF > /usr/local/bin/set-resolution
+#!/bin/bash
+RESOLUTION=\${1:-\${VNC_RESOLUTION:-1920x1080}}
+DPI=\${2:-\${VNC_DPI:-96}}
+IGNORE_ERROR=\${3:-"false"}
+if [ -z "\$1" ]; then
+ echo -e "**Current Settings **\n"
+ xrandr
+ echo -n -e "\nEnter new resolution (WIDTHxHEIGHT, blank for \${RESOLUTION}, Ctrl+C to abort).\n> "
+ read NEW_RES
+ if [ "\${NEW_RES}" != "" ]; then
+ RESOLUTION=\${NEW_RES}
+ fi
+ if ! echo "\${RESOLUTION}" | grep -E '[0-9]+x[0-9]+' > /dev/null; then
+ echo -e "\nInvalid resolution format!\n"
+ exit 1
+ fi
+ if [ -z "\$2" ]; then
+ echo -n -e "\nEnter new DPI (blank for \${DPI}, Ctrl+C to abort).\n> "
+ read NEW_DPI
+ if [ "\${NEW_DPI}" != "" ]; then
+ DPI=\${NEW_DPI}
+ fi
+ fi
+fi
+
+xrandr --fb \${RESOLUTION} --dpi \${DPI} > /dev/null 2>&1
+
+if [ \$? -ne 0 ] && [ "\${IGNORE_ERROR}" != "true" ]; then
+ echo -e "\nFAILED TO SET RESOLUTION!\n"
+ exit 1
+fi
+
+echo -e "\nSuccess!\n"
+EOF
+
+# Container ENTRYPOINT script
+cat << EOF > /usr/local/share/desktop-init.sh
+#!/bin/bash
+
+user_name="${USERNAME}"
+group_name="$(id -gn ${USERNAME})"
+LOG=/tmp/container-init.log
+
+export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-"autolaunch:"}"
+export DISPLAY="${DISPLAY:-:1}"
+export VNC_RESOLUTION="${VNC_RESOLUTION:-1440x768x16}"
+export LANG="${LANG:-"en_US.UTF-8"}"
+export LANGUAGE="${LANGUAGE:-"en_US.UTF-8"}"
+
+# Execute the command it not already running
+startInBackgroundIfNotRunning()
+{
+ log "Starting \$1."
+ echo -e "\n** \$(date) **" | sudoIf tee -a /tmp/\$1.log > /dev/null
+ if ! pidof \$1 > /dev/null; then
+ keepRunningInBackground "\$@"
+ while ! pidof \$1 > /dev/null; do
+ sleep 1
+ done
+ log "\$1 started."
+ else
+ echo "\$1 is already running." | sudoIf tee -a /tmp/\$1.log > /dev/null
+ log "\$1 is already running."
+ fi
+}
+
+# Keep command running in background
+keepRunningInBackground()
+{
+ (\$2 bash -c "while :; do echo [\\\$(date)] Process started.; \$3; echo [\\\$(date)] Process exited!; sleep 5; done 2>&1" | sudoIf tee -a /tmp/\$1.log > /dev/null & echo "\$!" | sudoIf tee /tmp/\$1.pid > /dev/null)
+}
+
+# Use sudo to run as root when required
+sudoIf()
+{
+ if [ "\$(id -u)" -ne 0 ]; then
+ sudo "\$@"
+ else
+ "\$@"
+ fi
+}
+
+# Use sudo to run as non-root user if not already running
+sudoUserIf()
+{
+ if [ "\$(id -u)" -eq 0 ] && [ "\${user_name}" != "root" ]; then
+ sudo -u \${user_name} "\$@"
+ else
+ "\$@"
+ fi
+}
+
+# Log messages
+log()
+{
+ echo -e "[\$(date)] \$@" | sudoIf tee -a \$LOG > /dev/null
+}
+
+log "** SCRIPT START **"
+
+# Start dbus.
+log 'Running "/etc/init.d/dbus start".'
+if [ -f "/var/run/dbus/pid" ] && ! pidof dbus-daemon > /dev/null; then
+ sudoIf rm -f /var/run/dbus/pid
+fi
+sudoIf /etc/init.d/dbus start 2>&1 | sudoIf tee -a /tmp/dbus-daemon-system.log > /dev/null
+while ! pidof dbus-daemon > /dev/null; do
+ sleep 1
+done
+
+# Startup tigervnc server and fluxbox
+sudo rm -rf /tmp/.X11-unix /tmp/.X*-lock
+mkdir -p /tmp/.X11-unix
+sudoIf chmod 1777 /tmp/.X11-unix
+sudoIf chown root:\${group_name} /tmp/.X11-unix
+if [ "\$(echo "\${VNC_RESOLUTION}" | tr -cd 'x' | wc -c)" = "1" ]; then VNC_RESOLUTION=\${VNC_RESOLUTION}x16; fi
+screen_geometry="\${VNC_RESOLUTION%*x*}"
+screen_depth="\${VNC_RESOLUTION##*x}"
+startInBackgroundIfNotRunning "Xtigervnc" sudoUserIf "tigervncserver \${DISPLAY} -geometry \${screen_geometry} -depth \${screen_depth} -rfbport ${VNC_PORT} -dpi \${VNC_DPI:-96} -localhost -desktop fluxbox -fg -passwd /usr/local/etc/vscode-dev-containers/vnc-passwd"
+
+# Spin up noVNC if installed and not runnning.
+if [ -d "/usr/local/novnc" ] && [ "\$(ps -ef | grep /usr/local/novnc/noVNC*/utils/launch.sh | grep -v grep)" = "" ]; then
+ keepRunningInBackground "noVNC" sudoIf "/usr/local/novnc/noVNC*/utils/launch.sh --listen ${NOVNC_PORT} --vnc localhost:${VNC_PORT}"
+ log "noVNC started."
+else
+ log "noVNC is already running or not installed."
+fi
+
+# Run whatever was passed in
+log "Executing \"\$@\"."
+exec "\$@"
+log "** SCRIPT EXIT **"
+EOF
+
+echo "${VNC_PASSWORD}" | vncpasswd -f > /usr/local/etc/vscode-dev-containers/vnc-passwd
+chmod +x /usr/local/share/desktop-init.sh /usr/local/bin/set-resolution
+
+# Set up fluxbox config
+copy_fluxbox_config "/root"
+if [ "${USERNAME}" != "root" ]; then
+ copy_fluxbox_config "/home/${USERNAME}"
+ chown -R ${USERNAME} /home/${USERNAME}/.Xmodmap /home/${USERNAME}/.fluxbox
+fi
+
+cat << EOF
+
+
+You now have a working desktop! Connect to in one of the following ways:
+
+- Forward port ${NOVNC_PORT} and use a web browser start the noVNC client (recommended)
+- Forward port ${VNC_PORT} using VS Code client and connect using a VNC Viewer
+
+In both cases, use the password "${VNC_PASSWORD}" when connecting
+
+(*) Done!
+
+EOF
+
diff --git a/collection/docker-from-docker/feature.json b/collection/docker-from-docker/feature.json
new file mode 100644
index 0000000..c7d684e
--- /dev/null
+++ b/collection/docker-from-docker/feature.json
@@ -0,0 +1,41 @@
+{
+ "id": "docker-from-docker",
+ "name": "Docker (Moby) support, reuse host Docker Engine (Docker-from-Docker)",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "20.10"],
+ "default": "latest",
+ "description": "Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.)"
+ },
+ "moby": {
+ "type": "boolean",
+ "default": true,
+ "description": "Install OSS Moby build instead of Docker CE"
+ },
+ "dockerDashComposeVersion": {
+ "type": "string",
+ "enum": ["v1", "v2" ],
+ "default": "v1",
+ "description": "Compose version to use for docker-compose (v1 or v2)"
+ }
+ },
+ "entrypoint": "/usr/local/share/docker-init.sh",
+ "containerEnv": {
+ "DOCKER_BUILDKIT": "1"
+ },
+ "extensions": [
+ "ms-azuretools.vscode-docker"
+ ],
+ "mounts": [
+ {
+ "source":"/var/run/docker.sock",
+ "target":"/var/run/docker-host.sock",
+ "type":"bind"
+ }
+ ],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/docker-from-docker/install.sh b/collection/docker-from-docker/install.sh
new file mode 100644
index 0000000..1102ae9
--- /dev/null
+++ b/collection/docker-from-docker/install.sh
@@ -0,0 +1,354 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./docker-debian.sh [enable non-root docker socket access flag] [source socket] [target socket] [non-root user] [use moby] [CLI version] [Major version for docker-compose]
+
+ENABLE_NONROOT_DOCKER=${1:-"true"}
+SOURCE_SOCKET=${2:-"/var/run/docker-host.sock"}
+TARGET_SOCKET=${3:-"/var/run/docker.sock"}
+USERNAME=${4:-"automatic"}
+USE_MOBY=${5:-"true"}
+DOCKER_VERSION=${6:-"latest"}
+DOCKER_DASH_COMPOSE_VERSION=${7:-"v1"} # v1 or v2
+MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
+DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal jammy"
+DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal hirsute impish jammy"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+# Install dependencies
+check_packages apt-transport-https curl ca-certificates gnupg2 dirmngr
+if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install git
+fi
+
+# Source /etc/os-release to get OS info
+. /etc/os-release
+# Fetch host/container arch.
+architecture="$(dpkg --print-architecture)"
+
+# Check if distro is suppported
+if [ "${USE_MOBY}" = "true" ]; then
+ # 'get_common_setting' allows attribute to be updated remotely
+ get_common_setting DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES
+ if [[ "${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then
+ err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS distribution"
+ err "Support distributions include: ${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}"
+ exit 1
+ fi
+ echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}'"
+else
+ get_common_setting DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES
+ if [[ "${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then
+ err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, please choose a compatible OS distribution"
+ err "Support distributions include: ${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}"
+ exit 1
+ fi
+ echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}'"
+fi
+
+# Set up the necessary apt repos (either Microsoft's or Docker's)
+if [ "${USE_MOBY}" = "true" ]; then
+
+ cli_package_name="moby-cli"
+
+ # Import key safely and import Microsoft apt repo
+ get_common_setting MICROSOFT_GPG_KEYS_URI
+ curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
+ echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list
+else
+ # Name of proprietary engine package
+ cli_package_name="docker-ce-cli"
+
+ # Import key safely and import Docker apt repo
+ curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor > /usr/share/keyrings/docker-archive-keyring.gpg
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
+fi
+
+# Refresh apt lists
+apt-get update
+
+# Soft version matching for CLI
+if [ "${DOCKER_VERSION}" = "latest" ] || [ "${DOCKER_VERSION}" = "lts" ] || [ "${DOCKER_VERSION}" = "stable" ]; then
+ # Empty, meaning grab whatever "latest" is in apt repo
+ cli_version_suffix=""
+else
+ # Fetch a valid version from the apt-cache (eg: the Microsoft repo appends +azure, breakfix, etc...)
+ docker_version_dot_escaped="${DOCKER_VERSION//./\\.}"
+ docker_version_dot_plus_escaped="${docker_version_dot_escaped//+/\\+}"
+ # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/
+ docker_version_regex="^(.+:)?${docker_version_dot_plus_escaped}([\\.\\+ ~:-]|$)"
+ set +e # Don't exit if finding version fails - will handle gracefully
+ cli_version_suffix="=$(apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${docker_version_regex}")"
+ set -e
+ if [ -z "${cli_version_suffix}" ] || [ "${cli_version_suffix}" = "=" ]; then
+ echo "(!) No full or partial Docker / Moby version match found for \"${DOCKER_VERSION}\" on OS ${ID} ${VERSION_CODENAME} (${architecture}). Available versions:"
+ apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+'
+ exit 1
+ fi
+ echo "cli_version_suffix ${cli_version_suffix}"
+fi
+
+# Install Docker / Moby CLI if not already installed
+if type docker > /dev/null 2>&1; then
+ echo "Docker / Moby CLI already installed."
+else
+ if [ "${USE_MOBY}" = "true" ]; then
+ apt-get -y install --no-install-recommends moby-cli${cli_version_suffix} moby-buildx
+ apt-get -y install --no-install-recommends moby-compose || echo "(*) Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping."
+ else
+ apt-get -y install --no-install-recommends docker-ce-cli${cli_version_suffix}
+ fi
+fi
+
+# Install Docker Compose if not already installed and is on a supported architecture
+if type docker-compose > /dev/null 2>&1; then
+ echo "Docker Compose already installed."
+else
+ TARGET_COMPOSE_ARCH="$(uname -m)"
+ if [ "${TARGET_COMPOSE_ARCH}" = "amd64" ]; then
+ TARGET_COMPOSE_ARCH="x86_64"
+ fi
+ if [ "${TARGET_COMPOSE_ARCH}" != "x86_64" ]; then
+ # Use pip to get a version that runns on this architecture
+ if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install python3-minimal python3-pip libffi-dev python3-venv
+ fi
+ export PIPX_HOME=/usr/local/pipx
+ mkdir -p ${PIPX_HOME}
+ export PIPX_BIN_DIR=/usr/local/bin
+ export PYTHONUSERBASE=/tmp/pip-tmp
+ export PIP_CACHE_DIR=/tmp/pip-tmp/cache
+ pipx_bin=pipx
+ if ! type pipx > /dev/null 2>&1; then
+ pip3 install --disable-pip-version-check --no-cache-dir --user pipx
+ pipx_bin=/tmp/pip-tmp/bin/pipx
+ fi
+ ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose
+ rm -rf /tmp/pip-tmp
+ else
+ compose_v1_version="1"
+ find_version_from_git_tags compose_v1_version "https://github.com/docker/compose" "tags/"
+ echo "(*) Installing docker-compose ${compose_v1_version}..."
+ curl -fsSL "https://github.com/docker/compose/releases/download/${compose_v1_version}/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose
+ chmod +x /usr/local/bin/docker-compose
+ fi
+fi
+
+# Install docker-compose switch if not already installed - https://github.com/docker/compose-switch#manual-installation
+current_v1_compose_path="$(which docker-compose)"
+target_v1_compose_path="$(dirname "${current_v1_compose_path}")/docker-compose-v1"
+if ! type compose-switch > /dev/null 2>&1; then
+ echo "(*) Installing compose-switch..."
+ compose_switch_version="latest"
+ find_version_from_git_tags compose_switch_version "https://github.com/docker/compose-switch"
+ curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/compose-switch
+ chmod +x /usr/local/bin/compose-switch
+ # TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11
+
+ # Setup v1 CLI as alternative in addition to compose-switch (which maps to v2)
+ mv "${current_v1_compose_path}" "${target_v1_compose_path}"
+ update-alternatives --install /usr/local/bin/docker-compose docker-compose /usr/local/bin/compose-switch 99
+ update-alternatives --install /usr/local/bin/docker-compose docker-compose "${target_v1_compose_path}" 1
+fi
+if [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then
+ update-alternatives --set docker-compose "${target_v1_compose_path}"
+else
+ update-alternatives --set docker-compose /usr/local/bin/compose-switch
+fi
+
+# If init file already exists, exit
+if [ -f "/usr/local/share/docker-init.sh" ]; then
+ exit 0
+fi
+echo "docker-init doesnt exist, adding..."
+
+# By default, make the source and target sockets the same
+if [ "${SOURCE_SOCKET}" != "${TARGET_SOCKET}" ]; then
+ touch "${SOURCE_SOCKET}"
+ ln -s "${SOURCE_SOCKET}" "${TARGET_SOCKET}"
+fi
+
+# Add a stub if not adding non-root user access, user is root
+if [ "${ENABLE_NONROOT_DOCKER}" = "false" ] || [ "${USERNAME}" = "root" ]; then
+ echo -e '#!/usr/bin/env bash\nexec "$@"' > /usr/local/share/docker-init.sh
+ chmod +x /usr/local/share/docker-init.sh
+ exit 0
+fi
+
+# Setup a docker group in the event the docker socket's group is not root
+if ! grep -qE '^docker:' /etc/group; then
+ groupadd --system docker
+fi
+usermod -aG docker "${USERNAME}"
+DOCKER_GID="$(grep -oP '^docker:x:\K[^:]+' /etc/group)"
+
+# If enabling non-root access and specified user is found, setup socat and add script
+chown -h "${USERNAME}":root "${TARGET_SOCKET}"
+if ! dpkg -s socat > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install socat
+fi
+tee /usr/local/share/docker-init.sh > /dev/null \
+<< EOF
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+
+set -e
+
+SOCAT_PATH_BASE=/tmp/vscr-docker-from-docker
+SOCAT_LOG=\${SOCAT_PATH_BASE}.log
+SOCAT_PID=\${SOCAT_PATH_BASE}.pid
+
+# Wrapper function to only use sudo if not already root
+sudoIf()
+{
+ if [ "\$(id -u)" -ne 0 ]; then
+ sudo "\$@"
+ else
+ "\$@"
+ fi
+}
+
+# Log messages
+log()
+{
+ echo -e "[\$(date)] \$@" | sudoIf tee -a \${SOCAT_LOG} > /dev/null
+}
+
+echo -e "\n** \$(date) **" | sudoIf tee -a \${SOCAT_LOG} > /dev/null
+log "Ensuring ${USERNAME} has access to ${SOURCE_SOCKET} via ${TARGET_SOCKET}"
+
+# If enabled, try to update the docker group with the right GID. If the group is root,
+# fall back on using socat to forward the docker socket to another unix socket so
+# that we can set permissions on it without affecting the host.
+if [ "${ENABLE_NONROOT_DOCKER}" = "true" ] && [ "${SOURCE_SOCKET}" != "${TARGET_SOCKET}" ] && [ "${USERNAME}" != "root" ] && [ "${USERNAME}" != "0" ]; then
+ SOCKET_GID=\$(stat -c '%g' ${SOURCE_SOCKET})
+ if [ "\${SOCKET_GID}" != "0" ] && [ "\${SOCKET_GID}" != "${DOCKER_GID}" ] && ! grep -E ".+:x:\${SOCKET_GID}" /etc/group; then
+ sudoIf groupmod --gid "\${SOCKET_GID}" docker
+ else
+ # Enable proxy if not already running
+ if [ ! -f "\${SOCAT_PID}" ] || ! ps -p \$(cat \${SOCAT_PID}) > /dev/null; then
+ log "Enabling socket proxy."
+ log "Proxying ${SOURCE_SOCKET} to ${TARGET_SOCKET} for vscode"
+ sudoIf rm -rf ${TARGET_SOCKET}
+ (sudoIf socat UNIX-LISTEN:${TARGET_SOCKET},fork,mode=660,user=${USERNAME} UNIX-CONNECT:${SOURCE_SOCKET} 2>&1 | sudoIf tee -a \${SOCAT_LOG} > /dev/null & echo "\$!" | sudoIf tee \${SOCAT_PID} > /dev/null)
+ else
+ log "Socket proxy already running."
+ fi
+ fi
+ log "Success"
+fi
+
+# Execute whatever commands were passed in (if any). This allows us
+# to set this script to ENTRYPOINT while still executing the default CMD.
+set +e
+exec "\$@"
+EOF
+chmod +x /usr/local/share/docker-init.sh
+chown ${USERNAME}:root /usr/local/share/docker-init.sh
+echo "Done!"
diff --git a/collection/docker-in-docker/feature.json b/collection/docker-in-docker/feature.json
new file mode 100644
index 0000000..0eaaa80
--- /dev/null
+++ b/collection/docker-in-docker/feature.json
@@ -0,0 +1,42 @@
+{
+ "id": "docker-in-docker",
+ "name": "Docker (Moby) support (Docker-in-Docker)",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "20.10" ],
+ "default": "latest",
+ "description": "Select or enter a Docker/Moby Engine version. (Availability can vary by OS version.)"
+ },
+ "moby": {
+ "type": "boolean",
+ "default": true,
+ "description": "Install OSS Moby build instead of Docker CE"
+ },
+ "dockerDashComposeVersion": {
+ "type": "string",
+ "enum": ["v1", "v2" ],
+ "default": "v1",
+ "description": "Default version of Docker Compose (v1 or v2)"
+ }
+ },
+ "entrypoint": "/usr/local/share/docker-init.sh",
+ "privileged": true,
+ "containerEnv": {
+ "DOCKER_BUILDKIT": "1"
+ },
+ "extensions": [
+ "ms-azuretools.vscode-docker"
+ ],
+ "mounts": [
+ {
+ "source":"dind-var-lib-docker",
+ "target":"/var/lib/docker",
+ "type":"volume"
+ }
+ ],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/docker-in-docker/install.sh b/collection/docker-in-docker/install.sh
new file mode 100644
index 0000000..a525203
--- /dev/null
+++ b/collection/docker-in-docker/install.sh
@@ -0,0 +1,397 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker-in-docker.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./docker-in-docker-debian.sh [enable non-root docker access flag] [non-root user] [use moby] [Engine/CLI Version] [Major version for docker-compose]
+
+ENABLE_NONROOT_DOCKER=${1:-"true"}
+USERNAME=${2:-"automatic"}
+USE_MOBY=${3:-"true"}
+DOCKER_VERSION=${4:-"latest"} # The Docker/Moby Engine + CLI should match in version
+DOCKER_DASH_COMPOSE_VERSION=${5:-"v1"} # v1 or v2
+MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
+DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal jammy"
+DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal hirsute impish jammy"
+
+# Default: Exit on any failure.
+set -e
+
+# Setup STDERR.
+err() {
+ echo "(!) $*" >&2
+}
+
+if [ "$(id -u)" -ne 0 ]; then
+ err 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+###################
+# Helper Functions
+# See: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/shared/utils.sh
+###################
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ err "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+###########################################
+# Start docker-in-docker installation
+###########################################
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+
+# Source /etc/os-release to get OS info
+. /etc/os-release
+# Fetch host/container arch.
+architecture="$(dpkg --print-architecture)"
+
+# Check if distro is suppported
+if [ "${USE_MOBY}" = "true" ]; then
+ # 'get_common_setting' allows attribute to be updated remotely
+ get_common_setting DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES
+ if [[ "${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then
+ err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS distribution"
+ err "Support distributions include: ${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}"
+ exit 1
+ fi
+ echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}'"
+else
+ get_common_setting DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES
+ if [[ "${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then
+ err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, please choose a compatible OS distribution"
+ err "Support distributions include: ${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}"
+ exit 1
+ fi
+ echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}'"
+fi
+
+# Install dependencies
+check_packages apt-transport-https curl ca-certificates pigz iptables gnupg2 dirmngr
+if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install git
+fi
+
+# Swap to legacy iptables for compatibility
+if type iptables-legacy > /dev/null 2>&1; then
+ update-alternatives --set iptables /usr/sbin/iptables-legacy
+ update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
+fi
+
+
+
+# Set up the necessary apt repos (either Microsoft's or Docker's)
+if [ "${USE_MOBY}" = "true" ]; then
+
+ # Name of open source engine/cli
+ engine_package_name="moby-engine"
+ cli_package_name="moby-cli"
+
+ # Import key safely and import Microsoft apt repo
+ get_common_setting MICROSOFT_GPG_KEYS_URI
+ curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
+ echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list
+else
+ # Name of licensed engine/cli
+ engine_package_name="docker-ce"
+ cli_package_name="docker-ce-cli"
+
+ # Import key safely and import Docker apt repo
+ curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor > /usr/share/keyrings/docker-archive-keyring.gpg
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
+fi
+
+# Refresh apt lists
+apt-get update
+
+# Soft version matching
+if [ "${DOCKER_VERSION}" = "latest" ] || [ "${DOCKER_VERSION}" = "lts" ] || [ "${DOCKER_VERSION}" = "stable" ]; then
+ # Empty, meaning grab whatever "latest" is in apt repo
+ engine_version_suffix=""
+ cli_version_suffix=""
+else
+ # Fetch a valid version from the apt-cache (eg: the Microsoft repo appends +azure, breakfix, etc...)
+ docker_version_dot_escaped="${DOCKER_VERSION//./\\.}"
+ docker_version_dot_plus_escaped="${docker_version_dot_escaped//+/\\+}"
+ # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/
+ docker_version_regex="^(.+:)?${docker_version_dot_plus_escaped}([\\.\\+ ~:-]|$)"
+ set +e # Don't exit if finding version fails - will handle gracefully
+ cli_version_suffix="=$(apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${docker_version_regex}")"
+ engine_version_suffix="=$(apt-cache madison ${engine_package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${docker_version_regex}")"
+ set -e
+ if [ -z "${engine_version_suffix}" ] || [ "${engine_version_suffix}" = "=" ] || [ -z "${cli_version_suffix}" ] || [ "${cli_version_suffix}" = "=" ] ; then
+ err "No full or partial Docker / Moby version match found for \"${DOCKER_VERSION}\" on OS ${ID} ${VERSION_CODENAME} (${architecture}). Available versions:"
+ apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+'
+ exit 1
+ fi
+ echo "engine_version_suffix ${engine_version_suffix}"
+ echo "cli_version_suffix ${cli_version_suffix}"
+fi
+
+# Install Docker / Moby CLI if not already installed
+if type docker > /dev/null 2>&1 && type dockerd > /dev/null 2>&1; then
+ echo "Docker / Moby CLI and Engine already installed."
+else
+ if [ "${USE_MOBY}" = "true" ]; then
+ # Install engine
+ set +e # Handle error gracefully
+ apt-get -y install --no-install-recommends moby-cli${cli_version_suffix} moby-buildx moby-engine${engine_version_suffix}
+ if [ $? -ne 0 ]; then
+ err "Packages for moby not available in OS ${ID} ${VERSION_CODENAME} (${architecture}). To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS version (eg: 'ubuntu-20.04')."
+ exit 1
+ fi
+ set -e
+
+ # Install compose
+ apt-get -y install --no-install-recommends moby-compose || err "Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping."
+ else
+ apt-get -y install --no-install-recommends docker-ce-cli${cli_version_suffix} docker-ce${engine_version_suffix}
+ fi
+fi
+
+echo "Finished installing docker / moby!"
+
+# Install Docker Compose if not already installed and is on a supported architecture
+if type docker-compose > /dev/null 2>&1; then
+ echo "Docker Compose v1 already installed."
+else
+ target_compose_arch="${architecture}"
+ if [ "${target_compose_arch}" = "amd64" ]; then
+ target_compose_arch="x86_64"
+ fi
+ if [ "${target_compose_arch}" != "x86_64" ]; then
+ # Use pip to get a version that runs on this architecture
+ if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install python3-minimal python3-pip libffi-dev python3-venv
+ fi
+ export PIPX_HOME=/usr/local/pipx
+ mkdir -p ${PIPX_HOME}
+ export PIPX_BIN_DIR=/usr/local/bin
+ export PYTHONUSERBASE=/tmp/pip-tmp
+ export PIP_CACHE_DIR=/tmp/pip-tmp/cache
+ pipx_bin=pipx
+ if ! type pipx > /dev/null 2>&1; then
+ pip3 install --disable-pip-version-check --no-cache-dir --user pipx
+ pipx_bin=/tmp/pip-tmp/bin/pipx
+ fi
+ ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose
+ rm -rf /tmp/pip-tmp
+ else
+ compose_v1_version="1"
+ find_version_from_git_tags compose_v1_version "https://github.com/docker/compose" "tags/"
+ echo "(*) Installing docker-compose ${compose_v1_version}..."
+ curl -fsSL "https://github.com/docker/compose/releases/download/${compose_v1_version}/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose
+ chmod +x /usr/local/bin/docker-compose
+ fi
+fi
+
+# Install docker-compose switch if not already installed - https://github.com/docker/compose-switch#manual-installation
+current_v1_compose_path="$(which docker-compose)"
+target_v1_compose_path="$(dirname "${current_v1_compose_path}")/docker-compose-v1"
+if ! type compose-switch > /dev/null 2>&1; then
+ echo "(*) Installing compose-switch..."
+ compose_switch_version="latest"
+ find_version_from_git_tags compose_switch_version "https://github.com/docker/compose-switch"
+ curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/compose-switch
+ chmod +x /usr/local/bin/compose-switch
+ # TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11
+
+ # Setup v1 CLI as alternative in addition to compose-switch (which maps to v2)
+ mv "${current_v1_compose_path}" "${target_v1_compose_path}"
+ update-alternatives --install /usr/local/bin/docker-compose docker-compose /usr/local/bin/compose-switch 99
+ update-alternatives --install /usr/local/bin/docker-compose docker-compose "${target_v1_compose_path}" 1
+fi
+if [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then
+ update-alternatives --set docker-compose "${target_v1_compose_path}"
+else
+ update-alternatives --set docker-compose /usr/local/bin/compose-switch
+fi
+
+# If init file already exists, exit
+if [ -f "/usr/local/share/docker-init.sh" ]; then
+ echo "/usr/local/share/docker-init.sh already exists, so exiting."
+ exit 0
+fi
+echo "docker-init doesnt exist, adding..."
+
+# Add user to the docker group
+if [ "${ENABLE_NONROOT_DOCKER}" = "true" ]; then
+ if ! getent group docker > /dev/null 2>&1; then
+ groupadd docker
+ fi
+
+ usermod -aG docker ${USERNAME}
+fi
+
+tee /usr/local/share/docker-init.sh > /dev/null \
+<< 'EOF'
+#!/bin/sh
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+
+set -e
+
+dockerd_start="$(cat << 'INNEREOF'
+ # explicitly remove dockerd and containerd PID file to ensure that it can start properly if it was stopped uncleanly
+ # ie: docker kill <ID>
+ find /run /var/run -iname 'docker*.pid' -delete || :
+ find /run /var/run -iname 'container*.pid' -delete || :
+
+ ## Dind wrapper script from docker team, adapted to a function
+ # Maintained: https://github.com/moby/moby/blob/master/hack/dind
+
+ export container=docker
+
+ if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then
+ mount -t securityfs none /sys/kernel/security || {
+ echo >&2 'Could not mount /sys/kernel/security.'
+ echo >&2 'AppArmor detection and --privileged mode might break.'
+ }
+ fi
+
+ # Mount /tmp (conditionally)
+ if ! mountpoint -q /tmp; then
+ mount -t tmpfs none /tmp
+ fi
+
+ # cgroup v2: enable nesting
+ if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
+ # move the processes from the root group to the /init group,
+ # otherwise writing subtree_control fails with EBUSY.
+ # An error during moving non-existent process (i.e., "cat") is ignored.
+ mkdir -p /sys/fs/cgroup/init
+ xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || :
+ # enable controllers
+ sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \
+ > /sys/fs/cgroup/cgroup.subtree_control
+ fi
+ ## Dind wrapper over.
+
+ # Handle DNS
+ set +e
+ cat /etc/resolv.conf | grep -i 'internal.cloudapp.net'
+ if [ $? -eq 0 ]
+ then
+ echo "Setting dockerd Azure DNS."
+ CUSTOMDNS="--dns 168.63.129.16"
+ else
+ echo "Not setting dockerd DNS manually."
+ CUSTOMDNS=""
+ fi
+ set -e
+
+ # Start docker/moby engine
+ ( dockerd $CUSTOMDNS > /tmp/dockerd.log 2>&1 ) &
+INNEREOF
+)"
+
+# Start using sudo if not invoked as root
+if [ "$(id -u)" -ne 0 ]; then
+ sudo /bin/sh -c "${dockerd_start}"
+else
+ eval "${dockerd_start}"
+fi
+
+set +e
+
+# Execute whatever commands were passed in (if any). This allows us
+# to set this script to ENTRYPOINT while still executing the default CMD.
+exec "$@"
+EOF
+
+chmod +x /usr/local/share/docker-init.sh
+chown ${USERNAME}:root /usr/local/share/docker-init.sh
+
+echo 'docker-in-docker-debian script has completed!' \ No newline at end of file
diff --git a/collection/dotnet/feature.json b/collection/dotnet/feature.json
new file mode 100644
index 0000000..b49e613
--- /dev/null
+++ b/collection/dotnet/feature.json
@@ -0,0 +1,28 @@
+{
+ "id": "dotnet",
+ "name": "Dotnet CLI",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest","6.0","5.0","3.1"],
+ "default": "latest",
+ "description": "Select or enter a dotnet CLI version. (Available versions may vary by Linux distribution.)"
+ },
+ "runtimeOnly": {
+ "type":"boolean",
+ "default": false,
+ "description": "Install just the dotnet runtime if true, and sdk if false."
+ }
+ },
+ "containerEnv": {
+ "DOTNET_ROOT": "/usr/local/dotnet",
+ "PATH": "${PATH}:${DOTNET_ROOT}"
+ },
+ "extensions": [
+ "ms-dotnettools.csharp"
+ ],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/dotnet/install.sh b/collection/dotnet/install.sh
new file mode 100644
index 0000000..f560186
--- /dev/null
+++ b/collection/dotnet/install.sh
@@ -0,0 +1,374 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/dotnet.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./dotnet-debian.sh [.NET version] [.NET runtime only] [non-root user] [add TARGET_DOTNET_ROOT to rc files flag] [.NET root] [access group name]
+
+DOTNET_VERSION=${1:-"latest"}
+DOTNET_RUNTIME_ONLY=${2:-"false"}
+USERNAME=${3:-"automatic"}
+UPDATE_RC=${4:-"true"}
+TARGET_DOTNET_ROOT=${5:-"/usr/local/dotnet"}
+ACCESS_GROUP=${6:-"dotnet"}
+
+MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
+DOTNET_ARCHIVE_ARCHITECTURES="amd64"
+DOTNET_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal hirsute"
+# Feed URI sourced from the official dotnet-install.sh
+# https://github.com/dotnet/install-scripts/blob/1b98b94a6f6d81cc4845eb88e0195fac67caa0a6/src/dotnet-install.sh#L1342-L1343
+DOTNET_CDN_FEED_URI="https://dotnetcli.azureedge.net"
+
+# Exit on failure.
+set -e
+
+# Setup STDERR.
+err() {
+ echo "(!) $*" >&2
+}
+
+# Ensure the appropriate root user is running the script.
+if [ "$(id -u)" -ne 0 ]; then
+ err 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user.
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
+ if id -u "${CURRENT_USER}" > /dev/null 2>&1; then
+ USERNAME="${CURRENT_USER}"
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+###################
+# Helper Functions
+###################
+
+# Cleanup temporary directory and associated files when exiting the script.
+cleanup() {
+ EXIT_CODE=$?
+ set +e
+ if [[ -n "${TMP_DIR}" ]]; then
+ echo "Executing cleanup of tmp files"
+ rm -Rf "${TMP_DIR}"
+ fi
+ exit $EXIT_CODE
+}
+trap cleanup EXIT
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Add TARGET_DOTNET_ROOT variable into PATH in bashrc/zshrc files.
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Run apt-get if needed.
+apt_get_update_if_needed() {
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Check if packages are installed and installs them if not.
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Get appropriate architecture name for .NET binaries for the target OS
+get_architecture_name_for_target_os() {
+ local architecture
+ architecture="$(uname -m)"
+ case $architecture in
+ x86_64) architecture="x64";;
+ aarch64 | armv8*) architecture="arm64";;
+ *) err "Architecture ${architecture} unsupported"; exit 1 ;;
+ esac
+
+ echo "${architecture}"
+}
+
+# Soft version matching that resolves a version for a given package in the *current apt-cache*
+# Return value is stored in first argument (the unprocessed version)
+apt_cache_package_and_version_soft_match() {
+ # Version
+ local version_variable_name="$1"
+ local requested_version=${!version_variable_name}
+ # Package Name
+ local package_variable_name="$2"
+ local partial_package_name=${!package_variable_name}
+ local package_name
+ # Exit on no match?
+ local exit_on_no_match="${3:-true}"
+ local major_minor_version
+
+ major_minor_version="$(echo "${requested_version}" | cut -d "." --field=1,2)"
+ package_name="$(apt-cache search "${partial_package_name}-[0-9].[0-9]" | awk -F" - " '{print $1}' | grep -m 1 "${partial_package_name}-${major_minor_version}")"
+
+ # Ensure we've exported useful variables
+ . /etc/os-release
+ local architecture="$(dpkg --print-architecture)"
+
+ dot_escaped="${requested_version//./\\.}"
+ dot_plus_escaped="${dot_escaped//+/\\+}"
+ # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/
+ version_regex="^(.+:)?${dot_plus_escaped}([\\.\\+ ~:-]|$)"
+ set +e # Don't exit if finding version fails - handle gracefully
+ fuzzy_version="$(apt-cache madison ${package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${version_regex}")"
+ set -e
+ if [ -z "${fuzzy_version}" ]; then
+ echo "(!) No full or partial for package \"${package_name}\" match found in apt-cache for \"${requested_version}\" on OS ${ID} ${VERSION_CODENAME} (${architecture})."
+
+ if $exit_on_no_match; then
+ echo "Available versions:"
+ apt-cache madison ${package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+'
+ exit 1 # Fail entire script
+ else
+ echo "Continuing to fallback method (if available)"
+ return 1;
+ fi
+ fi
+
+ # Globally assign fuzzy_version to this value
+ # Use this value as the return value of this function
+ declare -g ${version_variable_name}="=${fuzzy_version}"
+ echo "${version_variable_name} ${!version_variable_name}"
+
+ # Globally assign package to this value
+ # Use this value as the return value of this function
+ declare -g ${package_variable_name}="${package_name}"
+ echo "${package_variable_name} ${!package_variable_name}"
+}
+
+# Install .NET CLI using apt-get package installer
+install_using_apt() {
+ local sdk_or_runtime="$1"
+ local dotnet_major_minor_version
+ export DOTNET_PACKAGE="dotnet-${sdk_or_runtime}"
+
+ # Install dependencies
+ check_packages apt-transport-https curl ca-certificates gnupg2 dirmngr
+
+ # Import key safely and import Microsoft apt repo
+ get_common_setting MICROSOFT_GPG_KEYS_URI
+ curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
+ echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list
+ apt-get update
+
+ if [ "${DOTNET_VERSION}" = "latest" ] || [ "${DOTNET_VERSION}" = "lts" ]; then
+ DOTNET_VERSION=""
+ DOTNET_PACKAGE="${DOTNET_PACKAGE}-6.0"
+ else
+ # Sets DOTNET_VERSION and DOTNET_PACKAGE if matches found.
+ apt_cache_package_and_version_soft_match DOTNET_VERSION DOTNET_PACKAGE false
+ if [ "$?" != 0 ]; then
+ return 1
+ fi
+ fi
+
+ if ! (apt-get install -yq ${DOTNET_PACKAGE}${DOTNET_VERSION}); then
+ return 1
+ fi
+}
+
+# Find and extract .NET binary download details based on user-requested version
+# args:
+# sdk_or_runtime $1
+# exports:
+# DOTNET_DOWNLOAD_URL
+# DOTNET_DOWNLOAD_HASH
+# DOTNET_DOWNLOAD_NAME
+get_full_version_details() {
+ local sdk_or_runtime="$1"
+ local architecture
+ local dotnet_channel_version
+ local dotnet_releases_url
+ local dotnet_releases_json
+ local dotnet_latest_version
+ local dotnet_download_details
+
+ export DOTNET_DOWNLOAD_URL
+ export DOTNET_DOWNLOAD_HASH
+ export DOTNET_DOWNLOAD_NAME
+
+ # Set architecture variable to current user's architecture (x64 or ARM64).
+ architecture="$(get_architecture_name_for_target_os)"
+
+ # Set DOTNET_VERSION to empty string to ensure jq includes all .NET versions in reverse sort below
+ if [ "${DOTNET_VERSION}" = "latest" ]; then
+ DOTNET_VERSION=""
+ fi
+
+ dotnet_patchless_version="$(echo "${DOTNET_VERSION}" | cut -d "." --field=1,2)"
+
+ set +e
+ dotnet_channel_version="$(curl -s "${DOTNET_CDN_FEED_URI}/dotnet/release-metadata/releases-index.json" | jq -r --arg channel_version "${dotnet_patchless_version}" '[."releases-index"[]] | sort_by(."channel-version") | reverse | map( select(."channel-version" | startswith($channel_version))) | first | ."channel-version"')"
+ set -e
+
+ # Construct the releases URL using the official channel-version if one was found. Otherwise make a best-effort using the user input.
+ if [ -n "${dotnet_channel_version}" ] && [ "${dotnet_channel_version}" != "null" ]; then
+ dotnet_releases_url="${DOTNET_CDN_FEED_URI}/dotnet/release-metadata/${dotnet_channel_version}/releases.json"
+ else
+ dotnet_releases_url="${DOTNET_CDN_FEED_URI}/dotnet/release-metadata/${dotnet_patchless_version}/releases.json"
+ fi
+
+ set +e
+ dotnet_releases_json="$(curl -s "${dotnet_releases_url}")"
+ set -e
+
+ if [ -n "${dotnet_releases_json}" ] && [[ ! "${dotnet_releases_json}" = *"Error"* ]]; then
+ dotnet_latest_version="$(echo "${dotnet_releases_json}" | jq -r --arg sdk_or_runtime "${sdk_or_runtime}" '."latest-\($sdk_or_runtime)"')"
+ # If user-specified version has 2 or more dots, use it as is. Otherwise use latest version.
+ if [ "$(echo "${DOTNET_VERSION}" | grep -o "\." | wc -l)" -lt "2" ]; then
+ DOTNET_VERSION="${dotnet_latest_version}"
+ fi
+
+ dotnet_download_details="$(echo "${dotnet_releases_json}" | jq -r --arg sdk_or_runtime "${sdk_or_runtime}" --arg dotnet_version "${DOTNET_VERSION}" --arg arch "${architecture}" '.releases[]."\($sdk_or_runtime)" | select(.version==$dotnet_version) | .files[] | select(.name=="dotnet-\($sdk_or_runtime)-linux-\($arch).tar.gz")')"
+ if [ -n "${dotnet_download_details}" ]; then
+ echo "Found .NET binary version ${DOTNET_VERSION}"
+ DOTNET_DOWNLOAD_URL="$(echo "${dotnet_download_details}" | jq -r '.url')"
+ DOTNET_DOWNLOAD_HASH="$(echo "${dotnet_download_details}" | jq -r '.hash')"
+ DOTNET_DOWNLOAD_NAME="$(echo "${dotnet_download_details}" | jq -r '.name')"
+ else
+ err "Unable to find .NET binary for version ${DOTNET_VERSION}"
+ exit 1
+ fi
+ else
+ err "Unable to find .NET release details for version ${DOTNET_VERSION} at ${dotnet_releases_url}"
+ exit 1
+ fi
+}
+
+# Install .NET CLI using the .NET releases url
+install_using_dotnet_releases_url() {
+ local sdk_or_runtime="$1"
+
+ # Check listed package dependecies and install them if they are not already installed.
+ # NOTE: icu-devtools is a small package with similar dependecies to .NET.
+ # It will install the appropriate dependencies based on the OS:
+ # - libgcc-s1 OR libgcc1 depending on OS
+ # - the latest libicuXX depending on OS (eg libicu57 for stretch)
+ # - also installs libc6 and libstdc++6 which are required by .NET
+ check_packages curl ca-certificates tar jq icu-devtools libgssapi-krb5-2 libssl1.1 zlib1g
+
+ get_full_version_details "${sdk_or_runtime}"
+ # exports DOTNET_DOWNLOAD_URL, DOTNET_DOWNLOAD_HASH, DOTNET_DOWNLOAD_NAME
+ echo "DOWNLOAD LINK: ${DOTNET_DOWNLOAD_URL}"
+
+ # Setup the access group and add the user to it.
+ umask 0002
+ if ! cat /etc/group | grep -e "^${ACCESS_GROUP}:" > /dev/null 2>&1; then
+ groupadd -r "${ACCESS_GROUP}"
+ fi
+ usermod -a -G "${ACCESS_GROUP}" "${USERNAME}"
+
+ # Download the .NET binaries.
+ echo "DOWNLOADING BINARY..."
+ TMP_DIR="/tmp/dotnetinstall"
+ mkdir -p "${TMP_DIR}"
+ curl -sSL "${DOTNET_DOWNLOAD_URL}" -o "${TMP_DIR}/${DOTNET_DOWNLOAD_NAME}"
+
+ # Get checksum from .NET CLI blob storage using the runtime version and
+ # run validation (sha512sum) of checksum against the expected checksum hash.
+ echo "VERIFY CHECKSUM"
+ cd "${TMP_DIR}"
+ echo "${DOTNET_DOWNLOAD_HASH} *${DOTNET_DOWNLOAD_NAME}" | sha512sum -c -
+
+ # Extract binaries and add to path.
+ mkdir -p "${TARGET_DOTNET_ROOT}"
+ echo "Extract Binary to ${TARGET_DOTNET_ROOT}"
+ tar -xzf "${TMP_DIR}/${DOTNET_DOWNLOAD_NAME}" -C "${TARGET_DOTNET_ROOT}" --strip-components=1
+
+ updaterc "$(cat << EOF
+ export DOTNET_ROOT="${TARGET_DOTNET_ROOT}"
+ if [[ "\${PATH}" != *"\${DOTNET_ROOT}"* ]]; then export PATH="\${PATH}:\${DOTNET_ROOT}"; fi
+EOF
+ )"
+
+ # Give write permissions to the user.
+ chown -R ":${ACCESS_GROUP}" "${TARGET_DOTNET_ROOT}"
+ chmod g+r+w+s "${TARGET_DOTNET_ROOT}"
+ chmod -R g+r+w "${TARGET_DOTNET_ROOT}"
+}
+
+###########################
+# Start .NET installation
+###########################
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Determine if the user wants to download .NET Runtime only, or .NET SDK & Runtime
+# and set the appropriate variables.
+if [ "${DOTNET_RUNTIME_ONLY}" = "true" ]; then
+ DOTNET_SDK_OR_RUNTIME="runtime"
+elif [ "${DOTNET_RUNTIME_ONLY}" = "false" ]; then
+ DOTNET_SDK_OR_RUNTIME="sdk"
+else
+ err "Expected true for installing dotnet Runtime only or false for installing SDK and Runtime. Received ${DOTNET_RUNTIME_ONLY}."
+ exit 1
+fi
+
+# Install the .NET CLI
+echo "(*) Installing .NET CLI..."
+
+. /etc/os-release
+architecture="$(dpkg --print-architecture)"
+
+use_dotnet_releases_url="false"
+if [[ "${DOTNET_ARCHIVE_ARCHITECTURES}" = *"${architecture}"* ]] && [[ "${DOTNET_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]]; then
+ install_using_apt "${DOTNET_SDK_OR_RUNTIME}" || use_dotnet_releases_url="true"
+else
+ use_dotnet_releases_url="true"
+fi
+
+if [ "${use_dotnet_releases_url}" = "true" ]; then
+ install_using_dotnet_releases_url "${DOTNET_SDK_OR_RUNTIME}"
+fi
+
+echo "Done!" \ No newline at end of file
diff --git a/collection/git-lfs/feature.json b/collection/git-lfs/feature.json
new file mode 100644
index 0000000..4fa33c5
--- /dev/null
+++ b/collection/git-lfs/feature.json
@@ -0,0 +1,16 @@
+{
+ "id": "git-lfs",
+ "name": "Git Large File Support (LFS)",
+ "options": {
+ "version": {
+ "type": "string",
+ "enum": ["latest"],
+ "default": "latest",
+ "description": "Currently unused."
+ }
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/git-lfs/install.sh b/collection/git-lfs/install.sh
new file mode 100644
index 0000000..dd9e702
--- /dev/null
+++ b/collection/git-lfs/install.sh
@@ -0,0 +1,194 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/git-lfs.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./git-lfs-debian.sh [version]
+
+GIT_LFS_VERSION=${1:-"latest"}
+GIT_LFS_ARCHIVE_GPG_KEY_URI="https://packagecloud.io/github/git-lfs/gpgkey"
+GIT_LFS_ARCHIVE_ARCHITECTURES="amd64"
+GIT_LFS_ARCHIVE_VERSION_CODENAMES="stretch buster bullseye bionic focal"
+GIT_LFS_CHECKSUM_GPG_KEYS="0x88ace9b29196305ba9947552f1ba225c0223b187 0x86cd3297749375bcf8206715f54fe648088335a9 0xaa3b3450295830d2de6db90caba67be5a5795889"
+GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
+keyserver hkps://keys.openpgp.org
+keyserver hkp://keyserver.pgp.com"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Import the specified key in a variable name passed in as
+receive_gpg_keys() {
+ get_common_setting $1
+ local keys=${!1}
+ get_common_setting GPG_KEY_SERVERS true
+
+ # Use a temporary locaiton for gpg keys to avoid polluting image
+ export GNUPGHOME="/tmp/tmp-gnupg"
+ mkdir -p ${GNUPGHOME}
+ chmod 700 ${GNUPGHOME}
+ echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
+ # GPG key download sometimes fails for some reason and retrying fixes it.
+ local retry_count=0
+ local gpg_ok="false"
+ set +e
+ until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
+ do
+ echo "(*) Downloading GPG key..."
+ ( echo "${keys}" | xargs -n 1 gpg --recv-keys) 2>&1 && gpg_ok="true"
+ if [ "${gpg_ok}" != "true" ]; then
+ echo "(*) Failed getting key, retring in 10s..."
+ (( retry_count++ ))
+ sleep 10s
+ fi
+ done
+ set -e
+ if [ "${gpg_ok}" = "false" ]; then
+ echo "(!) Failed to get gpg key."
+ exit 1
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+install_using_apt() {
+ # Soft version matching
+ if [ "${GIT_LFS_VERSION}" != "latest" ] && [ "${GIT_LFS_VERSION}" != "lts" ] && [ "${GIT_LFS_VERSION}" != "stable" ]; then
+ find_version_from_git_tags GIT_LFS_VERSION "https://github.com/git-lfs/git-lfs"
+ version_suffix="=${GIT_LFS_VERSION}"
+ else
+ version_suffix=""
+ fi
+ # Install
+ get_common_setting GIT_LFS_ARCHIVE_GPG_KEY_URI
+ curl -sSL "${GIT_LFS_ARCHIVE_GPG_KEY_URI}" | gpg --dearmor > /usr/share/keyrings/gitlfs-archive-keyring.gpg
+ echo -e "deb [arch=${architecture} signed-by=/usr/share/keyrings/gitlfs-archive-keyring.gpg] https://packagecloud.io/github/git-lfs/${ID} ${VERSION_CODENAME} main\ndeb-src [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gitlfs-archive-keyring.gpg] https://packagecloud.io/github/git-lfs/${ID} ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/git-lfs.list
+
+ if ! (apt-get update && apt-get install -yq git-lfs${version_suffix}); then
+ rm -f /etc/apt/sources.list.d/git-lfs.list
+ return 1
+ fi
+
+ git-lfs install --skip-repo
+}
+
+install_using_github() {
+ echo "(*) No apt package for ${VERSION_CODENAME} ${architecture}. Installing manually."
+ mkdir -p /tmp/git-lfs
+ cd /tmp/git-lfs
+ find_version_from_git_tags GIT_LFS_VERSION "https://github.com/git-lfs/git-lfs"
+ git_lfs_filename="git-lfs-linux-${architecture}-v${GIT_LFS_VERSION}.tar.gz"
+ curl -sSL -o "${git_lfs_filename}" "https://github.com/git-lfs/git-lfs/releases/download/v${GIT_LFS_VERSION}/${git_lfs_filename}"
+ # Verify file
+ curl -sSL -o "sha256sums.asc" "https://github.com/git-lfs/git-lfs/releases/download/v${GIT_LFS_VERSION}/sha256sums.asc"
+ receive_gpg_keys GIT_LFS_CHECKSUM_GPG_KEYS
+ gpg -q --decrypt "sha256sums.asc" > sha256sums
+ sha256sum --ignore-missing -c "sha256sums"
+ # Extract and install
+ tar xf "${git_lfs_filename}" -C .
+ ./install.sh
+ rm -rf /tmp/git-lfs /tmp/tmp-gnupg
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Install git, curl, gpg, dirmngr and debian-archive-keyring if missing
+. /etc/os-release
+check_packages curl ca-certificates gnupg2 dirmngr apt-transport-https
+if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends git
+fi
+if [ "${ID}" = "debian" ]; then
+ check_packages debian-archive-keyring
+fi
+
+# Install Git LFS
+echo "Installing Git LFS..."
+architecture="$(dpkg --print-architecture)"
+if [[ "${GIT_LFS_ARCHIVE_ARCHITECTURES}" = *"${architecture}"* ]] && [[ "${GIT_LFS_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]]; then
+ install_using_apt || use_github="true"
+else
+ use_github="true"
+fi
+
+# If no archive exists or apt install fails, try direct from github
+if [ "${use_github}" = "true" ]; then
+ install_using_github
+fi
+
+echo "Done!" \ No newline at end of file
diff --git a/collection/git/feature.json b/collection/git/feature.json
new file mode 100644
index 0000000..6d9686b
--- /dev/null
+++ b/collection/git/feature.json
@@ -0,0 +1,21 @@
+{
+ "id": "git",
+ "name": "Git (may require compilation)",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "os-provided"],
+ "default": "os-provided",
+ "description": "Select or enter a Git version."
+ },
+ "ppa": {
+ "type": "boolean",
+ "default": true,
+ "description": "Install from PPA if available"
+ }
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/git/install.sh b/collection/git/install.sh
new file mode 100644
index 0000000..6900897
--- /dev/null
+++ b/collection/git/install.sh
@@ -0,0 +1,153 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/git-from-src.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./git-from-src-debian.sh [version] [use PPA if available]
+
+GIT_VERSION=${1:-"latest"} # 'system' checks the base image first, else installs 'latest'
+USE_PPA_IF_AVAILABLE=${2:-"false"}
+
+GIT_CORE_PPA_ARCHIVE_GPG_KEY=E1DD270288B4E6030699E45FA1715D88E1DF1F24
+GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
+keyserver hkps://keys.openpgp.org
+keyserver hkp://keyserver.pgp.com"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Import the specified key in a variable name passed in as
+receive_gpg_keys() {
+ get_common_setting $1
+ local keys=${!1}
+ get_common_setting GPG_KEY_SERVERS true
+ local keyring_args=""
+ if [ ! -z "$2" ]; then
+ mkdir -p "$(dirname \"$2\")"
+ keyring_args="--no-default-keyring --keyring $2"
+ fi
+
+ # Use a temporary locaiton for gpg keys to avoid polluting image
+ export GNUPGHOME="/tmp/tmp-gnupg"
+ mkdir -p ${GNUPGHOME}
+ chmod 700 ${GNUPGHOME}
+ echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
+ # GPG key download sometimes fails for some reason and retrying fixes it.
+ local retry_count=0
+ local gpg_ok="false"
+ set +e
+ until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
+ do
+ echo "(*) Downloading GPG key..."
+ ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true"
+ if [ "${gpg_ok}" != "true" ]; then
+ echo "(*) Failed getting key, retring in 10s..."
+ (( retry_count++ ))
+ sleep 10s
+ fi
+ done
+ set -e
+ if [ "${gpg_ok}" = "false" ]; then
+ echo "(!) Failed to get gpg key."
+ exit 1
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Source /etc/os-release to get OS info
+. /etc/os-release
+
+# If the os provided version is "good enough", just install that.
+if [ ${GIT_VERSION} = "os-provided" ] || [ ${GIT_VERSION} = "system" ]; then
+ if type git > /dev/null 2>&1; then
+ echo "Detected existing system install: $(git version)"
+ exit 0
+ fi
+
+ echo "Installing git from OS apt repository"
+ check_packages git
+ exit 0
+fi
+
+# If ubuntu, PPAs allowed, and latest - install from there
+if ([ "${GIT_VERSION}" = "latest" ] || [ "${GIT_VERSION}" = "lts" ] || [ "${GIT_VERSION}" = "current" ]) && [ "${ID}" = "ubuntu" ] && [ "${USE_PPA_IF_AVAILABLE}" = "true" ]; then
+ echo "Using PPA to install latest git..."
+ check_packages apt-transport-https curl ca-certificates gnupg2 dirmngr
+ receive_gpg_keys GIT_CORE_PPA_ARCHIVE_GPG_KEY /usr/share/keyrings/gitcoreppa-archive-keyring.gpg
+ echo -e "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gitcoreppa-archive-keyring.gpg] http://ppa.launchpad.net/git-core/ppa/ubuntu ${VERSION_CODENAME} main\ndeb-src [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gitcoreppa-archive-keyring.gpg] http://ppa.launchpad.net/git-core/ppa/ubuntu ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/git-core-ppa.list
+ apt-get update
+ apt-get -y install --no-install-recommends git
+ rm -rf "/tmp/tmp-gnupg"
+ exit 0
+fi
+
+# Install required packages to build if missing
+check_packages build-essential curl ca-certificates tar gettext libssl-dev zlib1g-dev libcurl?-openssl-dev libexpat1-dev
+
+# Partial version matching
+if [ "$(echo "${GIT_VERSION}" | grep -o '\.' | wc -l)" != "2" ]; then
+ requested_version="${GIT_VERSION}"
+ version_list="$(curl -sSL -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/git/git/tags" | grep -oP '"name":\s*"v\K[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | sort -rV )"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "current" ]; then
+ GIT_VERSION="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ GIT_VERSION="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ if [ -z "${GIT_VERSION}" ] || ! echo "${version_list}" | grep "^${GIT_VERSION//./\\.}$" > /dev/null 2>&1; then
+ echo "Invalid git version: ${requested_version}" >&2
+ exit 1
+ fi
+fi
+
+echo "Downloading source for ${GIT_VERSION}..."
+curl -sL https://github.com/git/git/archive/v${GIT_VERSION}.tar.gz | tar -xzC /tmp 2>&1
+echo "Building..."
+cd /tmp/git-${GIT_VERSION}
+make -s prefix=/usr/local all && make -s prefix=/usr/local install 2>&1
+rm -rf /tmp/git-${GIT_VERSION}
+echo "Done!"
diff --git a/collection/github-cli/feature.json b/collection/github-cli/feature.json
new file mode 100644
index 0000000..03f07cf
--- /dev/null
+++ b/collection/github-cli/feature.json
@@ -0,0 +1,16 @@
+{
+ "id": "github-cli",
+ "name": "GitHub CLI",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Select version of the GitHub CLI, if not latest."
+ }
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/github-cli/install.sh b/collection/github-cli/install.sh
new file mode 100644
index 0000000..01ad4d4
--- /dev/null
+++ b/collection/github-cli/install.sh
@@ -0,0 +1,188 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/github.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./github-debian.sh [version]
+
+CLI_VERSION=${1:-"latest"}
+
+GITHUB_CLI_ARCHIVE_GPG_KEY=C99B11DEB97541F0
+GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
+keyserver hkps://keys.openpgp.org
+keyserver hkp://keyserver.pgp.com"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Import the specified key in a variable name passed in as
+receive_gpg_keys() {
+ get_common_setting $1
+ local keys=${!1}
+ get_common_setting GPG_KEY_SERVERS true
+
+ # Use a temporary locaiton for gpg keys to avoid polluting image
+ export GNUPGHOME="/tmp/tmp-gnupg"
+ mkdir -p ${GNUPGHOME}
+ chmod 700 ${GNUPGHOME}
+ echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
+ # GPG key download sometimes fails for some reason and retrying fixes it.
+ local retry_count=0
+ local gpg_ok="false"
+ set +e
+ until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
+ do
+ echo "(*) Downloading GPG key..."
+ ( echo "${keys}" | xargs -n 1 gpg --recv-keys) 2>&1 && gpg_ok="true"
+ if [ "${gpg_ok}" != "true" ]; then
+ echo "(*) Failed getting key, retring in 10s..."
+ (( retry_count++ ))
+ sleep 10s
+ fi
+ done
+ set -e
+ if [ "${gpg_ok}" = "false" ]; then
+ echo "(!) Failed to get gpg key."
+ exit 1
+ fi
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Import the specified key in a variable name passed in as
+receive_gpg_keys() {
+ get_common_setting $1
+ local keys=${!1}
+ get_common_setting GPG_KEY_SERVERS true
+ local keyring_args=""
+ if [ ! -z "$2" ]; then
+ keyring_args="--no-default-keyring --keyring $2"
+ fi
+
+ # Use a temporary locaiton for gpg keys to avoid polluting image
+ export GNUPGHOME="/tmp/tmp-gnupg"
+ mkdir -p ${GNUPGHOME}
+ chmod 700 ${GNUPGHOME}
+ echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
+ # GPG key download sometimes fails for some reason and retrying fixes it.
+ local retry_count=0
+ local gpg_ok="false"
+ set +e
+ until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
+ do
+ echo "(*) Downloading GPG key..."
+ ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true"
+ if [ "${gpg_ok}" != "true" ]; then
+ echo "(*) Failed getting key, retring in 10s..."
+ (( retry_count++ ))
+ sleep 10s
+ fi
+ done
+ set -e
+ if [ "${gpg_ok}" = "false" ]; then
+ echo "(!) Failed to get gpg key."
+ exit 1
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Install curl, apt-transport-https, curl, gpg, or dirmngr, git if missing
+check_packages curl ca-certificates apt-transport-https dirmngr gnupg2
+if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends git
+fi
+
+# Soft version matching
+if [ "${CLI_VERSION}" != "latest" ] && [ "${CLI_VERSION}" != "lts" ] && [ "${CLI_VERSION}" != "stable" ]; then
+ find_version_from_git_tags CLI_VERSION "https://github.com/cli/cli"
+ version_suffix="=${CLI_VERSION}"
+else
+ version_suffix=""
+fi
+
+# Install the GitHub CLI
+echo "Downloading github CLI..."
+# Import key safely (new method rather than deprecated apt-key approach) and install
+. /etc/os-release
+receive_gpg_keys GITHUB_CLI_ARCHIVE_GPG_KEY /usr/share/keyrings/githubcli-archive-keyring.gpg
+echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list
+apt-get update
+apt-get -y install "gh${version_suffix}"
+rm -rf "/tmp/gh/gnupg"
+echo "Done!"
diff --git a/collection/go/feature.json b/collection/go/feature.json
new file mode 100644
index 0000000..0f8c6db
--- /dev/null
+++ b/collection/go/feature.json
@@ -0,0 +1,24 @@
+{
+ "id": "go",
+ "name": "Go",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "1.18", "1.17"],
+ "default": "latest",
+ "description": "Select or enter a Go version to install"
+ }
+ },
+ "init": true,
+ "extensions": ["golang.Go"],
+ "containerEnv": {
+ "GOPATH": "/go",
+ "PATH": "${GOPATH}/bin:${GOROOT}/bin:${PATH}"
+ },
+ "capAdd": [ "SYS_PTRACE" ],
+ "securityOpt": [ "seccomp=unconfined" ],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/go/install.sh b/collection/go/install.sh
new file mode 100644
index 0000000..47ab299
--- /dev/null
+++ b/collection/go/install.sh
@@ -0,0 +1,255 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/go.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./go-debian.sh [Go version] [GOROOT] [GOPATH] [non-root user] [Add GOPATH, GOROOT to rc files flag] [Install tools flag]
+
+TARGET_GO_VERSION=${1:-"latest"}
+TARGET_GOROOT=${2:-"/usr/local/go"}
+TARGET_GOPATH=${3:-"/go"}
+USERNAME=${4:-"automatic"}
+UPDATE_RC=${5:-"true"}
+INSTALL_GO_TOOLS=${6:-"true"}
+
+# https://www.google.com/linuxrepositories/
+GO_GPG_KEY_URI="https://dl.google.com/linux/linux_signing_key.pub"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Install curl, tar, git, other dependencies if missing
+check_packages curl ca-certificates gnupg2 tar g++ gcc libc6-dev make pkg-config
+if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends git
+fi
+
+# Get closest match for version number specified
+find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true"
+
+architecture="$(uname -m)"
+case $architecture in
+ x86_64) architecture="amd64";;
+ aarch64 | armv8*) architecture="arm64";;
+ aarch32 | armv7* | armvhf*) architecture="armv6l";;
+ i?86) architecture="386";;
+ *) echo "(!) Architecture $architecture unsupported"; exit 1 ;;
+esac
+
+# Install Go
+umask 0002
+if ! cat /etc/group | grep -e "^golang:" > /dev/null 2>&1; then
+ groupadd -r golang
+fi
+usermod -a -G golang "${USERNAME}"
+mkdir -p "${TARGET_GOROOT}" "${TARGET_GOPATH}"
+if [ "${TARGET_GO_VERSION}" != "none" ] && ! type go > /dev/null 2>&1; then
+ # Use a temporary locaiton for gpg keys to avoid polluting image
+ export GNUPGHOME="/tmp/tmp-gnupg"
+ mkdir -p ${GNUPGHOME}
+ chmod 700 ${GNUPGHOME}
+ get_common_setting GO_GPG_KEY_URI
+ curl -sSL -o /tmp/tmp-gnupg/golang_key "${GO_GPG_KEY_URI}"
+ gpg -q --import /tmp/tmp-gnupg/golang_key
+ echo "Downloading Go ${TARGET_GO_VERSION}..."
+ set +e
+ curl -fsSL -o /tmp/go.tar.gz "https://golang.org/dl/go${TARGET_GO_VERSION}.linux-${architecture}.tar.gz"
+ exit_code=$?
+ set -e
+ if [ "$exit_code" != "0" ]; then
+ echo "(!) Download failed."
+ # Try one break fix version number less if we get a failure. Use "set +e" since "set -e" can cause failures in valid scenarios.
+ set +e
+ major="$(echo "${TARGET_GO_VERSION}" | grep -oE '^[0-9]+' || echo '')"
+ minor="$(echo "${TARGET_GO_VERSION}" | grep -oP '^[0-9]+\.\K[0-9]+' || echo '')"
+ breakfix="$(echo "${TARGET_GO_VERSION}" | grep -oP '^[0-9]+\.[0-9]+\.\K[0-9]+' 2>/dev/null || echo '')"
+ # Handle Go's odd version pattern where "0" releases omit the last part
+ if [ "${breakfix}" = "" ] || [ "${breakfix}" = "0" ]; then
+ ((minor=minor-1))
+ TARGET_GO_VERSION="${major}.${minor}"
+ # Look for latest version from previous minor release
+ find_version_from_git_tags TARGET_GO_VERSION "https://go.googlesource.com/go" "tags/go" "." "true"
+ else
+ ((breakfix=breakfix-1))
+ if [ "${breakfix}" = "0" ]; then
+ TARGET_GO_VERSION="${major}.${minor}"
+ else
+ TARGET_GO_VERSION="${major}.${minor}.${breakfix}"
+ fi
+ fi
+ set -e
+ echo "Trying ${TARGET_GO_VERSION}..."
+ curl -fsSL -o /tmp/go.tar.gz "https://golang.org/dl/go${TARGET_GO_VERSION}.linux-${architecture}.tar.gz"
+ fi
+ curl -fsSL -o /tmp/go.tar.gz.asc "https://golang.org/dl/go${TARGET_GO_VERSION}.linux-${architecture}.tar.gz.asc"
+ gpg --verify /tmp/go.tar.gz.asc /tmp/go.tar.gz
+ echo "Extracting Go ${TARGET_GO_VERSION}..."
+ tar -xzf /tmp/go.tar.gz -C "${TARGET_GOROOT}" --strip-components=1
+ rm -rf /tmp/go.tar.gz /tmp/go.tar.gz.asc /tmp/tmp-gnupg
+else
+ echo "Go already installed. Skipping."
+fi
+
+# Install Go tools that are isImportant && !replacedByGopls based on
+# https://github.com/golang/vscode-go/blob/v0.31.1/src/goToolsInformation.ts
+GO_TOOLS="\
+ golang.org/x/tools/gopls@latest \
+ honnef.co/go/tools/cmd/staticcheck@latest \
+ golang.org/x/lint/golint@latest \
+ github.com/mgechev/revive@latest \
+ github.com/uudashr/gopkgs/v2/cmd/gopkgs@latest \
+ github.com/ramya-rao-a/go-outline@latest \
+ github.com/go-delve/delve/cmd/dlv@latest \
+ github.com/golangci/golangci-lint/cmd/golangci-lint@latest"
+if [ "${INSTALL_GO_TOOLS}" = "true" ]; then
+ echo "Installing common Go tools..."
+ export PATH=${TARGET_GOROOT}/bin:${PATH}
+ mkdir -p /tmp/gotools /usr/local/etc/vscode-dev-containers ${TARGET_GOPATH}/bin
+ cd /tmp/gotools
+ export GOPATH=/tmp/gotools
+ export GOCACHE=/tmp/gotools/cache
+
+ # Use go get for versions of go under 1.16
+ go_install_command=install
+ if [[ "1.16" > "$(go version | grep -oP 'go\K[0-9]+\.[0-9]+(\.[0-9]+)?')" ]]; then
+ export GO111MODULE=on
+ go_install_command=get
+ echo "Go version < 1.16, using go get."
+ fi
+
+ (echo "${GO_TOOLS}" | xargs -n 1 go ${go_install_command} -v )2>&1 | tee -a /usr/local/etc/vscode-dev-containers/go.log
+
+ # Move Go tools into path and clean up
+ mv /tmp/gotools/bin/* ${TARGET_GOPATH}/bin/
+
+ rm -rf /tmp/gotools
+fi
+
+# Add GOPATH variable and bin directory into PATH in bashrc/zshrc files (unless disabled)
+updaterc "$(cat << EOF
+export GOPATH="${TARGET_GOPATH}"
+if [[ "\${PATH}" != *"\${GOPATH}/bin"* ]]; then export PATH="\${PATH}:\${GOPATH}/bin"; fi
+export GOROOT="${TARGET_GOROOT}"
+if [[ "\${PATH}" != *"\${GOROOT}/bin"* ]]; then export PATH="\${PATH}:\${GOROOT}/bin"; fi
+EOF
+)"
+
+chown -R :golang "${TARGET_GOROOT}" "${TARGET_GOPATH}"
+chmod -R g+r+w "${TARGET_GOROOT}" "${TARGET_GOPATH}"
+find "${TARGET_GOROOT}" -type d | xargs -n 1 chmod g+s
+find "${TARGET_GOPATH}" -type d | xargs -n 1 chmod g+s
+
+echo "Done!"
+
diff --git a/collection/gradle/feature.json b/collection/gradle/feature.json
new file mode 100644
index 0000000..3a393b9
--- /dev/null
+++ b/collection/gradle/feature.json
@@ -0,0 +1,22 @@
+{
+ "id": "gradle",
+ "name": "Gradle (via SDKMAN!)",
+ "documentationURL": "https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/gradle.md",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "7", "6", "5"],
+ "default": "latest",
+ "description": "Select or enter a Gradle version to install"
+ }
+ },
+ "extensions": ["vscjava.vscode-java-pack"],
+ "containerEnv": {
+ "SDKMAN_DIR": "/usr/local/sdkman",
+ "PATH": "${SDKMAN_DIR}/bin:${SDKMAN_DIR}/candidates/gradle/current/bin:${PATH}"
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/gradle/install.sh b/collection/gradle/install.sh
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/collection/gradle/install.sh
diff --git a/collection/hugo/feature.json b/collection/hugo/feature.json
new file mode 100644
index 0000000..0b91ac1
--- /dev/null
+++ b/collection/hugo/feature.json
@@ -0,0 +1,16 @@
+{
+ "id": "hugo",
+ "name": "Hugo",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Select or enter a version."
+ }
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+}
diff --git a/collection/hugo/install.sh b/collection/hugo/install.sh
new file mode 100644
index 0000000..3913c7a
--- /dev/null
+++ b/collection/hugo/install.sh
@@ -0,0 +1,113 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/hugo.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./hugo-debian.sh [Hugo version] [HUGO_DIR] [Non-root user] [Add rc files flag]
+
+VERSION=${1:-"latest"}
+export HUGO_DIR=${2:-"/usr/local/hugo"}
+USERNAME=${3:-"automatic"}
+UPDATE_RC=${4:-"true"}
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+architecture="$(uname -m)"
+if [ "${architecture}" != "amd64" ] && [ "${architecture}" != "x86_64" ] && [ "${architecture}" != "arm64" ] && [ "${architecture}" != "aarch64" ]; then
+ echo "(!) Architecture $architecture unsupported"
+ exit 1
+fi
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Install dependencies
+check_packages curl ca-certificates tar
+
+# Fetch latest version of Hugo if needed
+if [ "${VERSION}" = "latest" ] || [ "${VERSION}" = "lts" ]; then
+ export VERSION=$(curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4)}')
+fi
+
+# Install Hugo if it's missing
+if ! hugo version &> /dev/null ; then
+ echo "Installing Hugo..."
+ installation_dir="$HUGO_DIR/bin"
+ mkdir -p "$installation_dir"
+
+ # Install ARM or x86 version of hugo based on current machine architecture
+ if [ "$(uname -m)" == "aarch64" ]; then
+ arch="ARM64"
+ else
+ arch="64bit"
+ fi
+
+ hugo_filename="hugo_${VERSION}_Linux-${arch}.tar.gz"
+
+ curl -fsSLO --compressed "https://github.com/gohugoio/hugo/releases/download/v${VERSION}/${hugo_filename}"
+ tar -xzf "$hugo_filename" -C "$installation_dir"
+ rm "$hugo_filename"
+
+ updaterc "export HUGO_DIR=${installation_dir}"
+fi
+
+echo "Done!"
diff --git a/collection/java/feature.json b/collection/java/feature.json
new file mode 100644
index 0000000..263c2c8
--- /dev/null
+++ b/collection/java/feature.json
@@ -0,0 +1,26 @@
+{
+ "id": "java",
+ "name": "Java (via SDKMAN!)",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["lts", "latest", "17", "11", "8"],
+ "default": "lts",
+ "description": "Select or enter a Java version to install"
+ }
+ },
+ "buildArg": "_VSC_INSTALL_JAVA",
+ "extensions": ["vscjava.vscode-java-pack"],
+ "containerEnv": {
+ "SDKMAN_DIR": "/usr/local/sdkman",
+ "PATH": "${SDKMAN_DIR}/bin:${SDKMAN_DIR}/candidates/java/current/bin:${PATH}"
+ },
+ "settings": {
+ "java.home": "/extension-java-home",
+ "java.import.gradle.java.home": "/usr/local/sdkman/candidates/java/current"
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+}
diff --git a/collection/java/install.sh b/collection/java/install.sh
new file mode 100644
index 0000000..f5880cd
--- /dev/null
+++ b/collection/java/install.sh
@@ -0,0 +1,145 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/java.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./java-debian.sh [JDK version] [SDKMAN_DIR] [non-root user] [Add to rc files flag]
+
+JAVA_VERSION=${1:-"lts"}
+export SDKMAN_DIR=${2:-"/usr/local/sdkman"}
+USERNAME=${3:-"automatic"}
+UPDATE_RC=${4:-"true"}
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Use SDKMAN to install something using a partial version match
+sdk_install() {
+ local install_type=$1
+ local requested_version=$2
+ local prefix=$3
+ local suffix="${4:-"\\s*"}"
+ local full_version_check=${5:-".*-[a-z]+"}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ # Blank will install latest stable version SDKMAN has
+ if [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "default" ]; then
+ requested_version=""
+ elif echo "${requested_version}" | grep -oE "${full_version_check}" > /dev/null 2>&1; then
+ echo "${requested_version}"
+ else
+ local regex="${prefix}\\K[0-9]+\\.[0-9]+\\.[0-9]+${suffix}"
+ local version_list="$(. ${SDKMAN_DIR}/bin/sdkman-init.sh && sdk list ${install_type} 2>&1 | grep -oP "${regex}" | tr -d ' ' | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ]; then
+ requested_version="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ requested_version="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ if [ -z "${requested_version}" ] || ! echo "${version_list}" | grep "^${requested_version//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Version $2 not found. Available versions:\n${version_list}" >&2
+ exit 1
+ fi
+ fi
+ su ${USERNAME} -c "umask 0002 && . ${SDKMAN_DIR}/bin/sdkman-init.sh && sdk install ${install_type} ${requested_version} && sdk flush archives && sdk flush temp"
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+architecture="$(uname -m)"
+if [ "${architecture}" != "amd64" ] && [ "${architecture}" != "x86_64" ] && [ "${architecture}" != "arm64" ] && [ "${architecture}" != "aarch64" ]; then
+ echo "(!) Architecture $architecture unsupported"
+ exit 1
+fi
+
+# Install dependencies
+check_packages curl ca-certificates zip unzip sed
+
+# Install sdkman if not installed
+if [ ! -d "${SDKMAN_DIR}" ]; then
+ # Create sdkman group, dir, and set sticky bit
+ if ! cat /etc/group | grep -e "^sdkman:" > /dev/null 2>&1; then
+ groupadd -r sdkman
+ fi
+ usermod -a -G sdkman ${USERNAME}
+ umask 0002
+ # Install SDKMAN
+ curl -sSL "https://get.sdkman.io?rcupdate=false" | bash
+ chown -R :sdkman ${SDKMAN_DIR}
+ find ${SDKMAN_DIR} -type d | xargs -d '\n' chmod g+s
+ # Add sourcing of sdkman into bashrc/zshrc files (unless disabled)
+ updaterc "export SDKMAN_DIR=${SDKMAN_DIR}\n. \${SDKMAN_DIR}/bin/sdkman-init.sh"
+fi
+
+# Use Microsoft JDK for everything but JDK 8
+jdk_distro="ms"
+if echo "${JAVA_VERSION}" | grep -E '^8([\s\.]|$)' > /dev/null 2>&1; then
+ jdk_distro="tem"
+fi
+if [ "${JAVA_VERSION}" = "lts" ]; then
+ JAVA_VERSION="17"
+fi
+sdk_install java ${JAVA_VERSION} "\\s*" "(\\.[a-z0-9]+)*-${jdk_distro}\\s*" ".*-[a-z]+$"
+
+echo "Done!" \ No newline at end of file
diff --git a/collection/jekyll/feature.json b/collection/jekyll/feature.json
new file mode 100644
index 0000000..852aa30
--- /dev/null
+++ b/collection/jekyll/feature.json
@@ -0,0 +1,16 @@
+{
+ "id": "jekyll",
+ "name": "Jekyll",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Select or enter a version."
+ }
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+}
diff --git a/collection/jekyll/install.sh b/collection/jekyll/install.sh
new file mode 100644
index 0000000..53efa82
--- /dev/null
+++ b/collection/jekyll/install.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/hugo.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./jekyll-debian.sh [Jekyll version] [Non-root user] [Add rc files flag]
+
+VERSION=${1:-"latest"}
+USERNAME=${2:-"automatic"}
+
+set -e
+
+# If in automatic mode, determine if a user already exists, if not use codespace
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=codespace
+ fi
+elif [ "${USERNAME}" = "none" ]; then
+ USERNAME=root
+ USER_UID=0
+ USER_GID=0
+fi
+
+# Use sudo to run as non-root user is not already running
+sudoUserIf()
+{
+ if [ "$(id -u)" -eq 0 ] && [ "${USERNAME}" != "root" ]; then
+ sudo -u ${USERNAME} "$@"
+ else
+ "$@"
+ fi
+}
+
+# If we don't yet have Ruby installed, exit.
+if ! /usr/local/rvm/rubies/default/bin/ruby --version > /dev/null ; then
+ echo "You need to install Ruby before installing Jekyll."
+ exit 1
+fi
+
+# If we don't already have Jekyll installed, install it now.
+if ! jekyll --version > /dev/null ; then
+ echo "Installing Jekyll..."
+ if [ "${VERSION}" = "latest" ]; then
+ PATH="/usr/local/rvm/rubies/default/bin:${PATH}" /usr/local/rvm/rubies/default/bin/gem install jekyll
+ else
+ PATH="/usr/local/rvm/rubies/default/bin:${PATH}" /usr/local/rvm/rubies/default/bin/gem install jekyll -v "${VERSION}"
+ fi
+fi
diff --git a/collection/jupyterlab/feature.json b/collection/jupyterlab/feature.json
new file mode 100644
index 0000000..e080339
--- /dev/null
+++ b/collection/jupyterlab/feature.json
@@ -0,0 +1,21 @@
+{
+ "id": "jupyterlab",
+ "name": "Jupyter Lab",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "3.6.2"],
+ "default": "latest",
+ "description": "Select or enter a jupyterlab version."
+ }
+ },
+ "extensions": [
+ "ms-python.python",
+ "ms-python.vscode-pylance",
+ "ms-toolsai.jupyter"
+ ],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/jupyterlab/install.sh b/collection/jupyterlab/install.sh
new file mode 100644
index 0000000..45267b0
--- /dev/null
+++ b/collection/jupyterlab/install.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+set -e
+
+VERSION=${1:-"latest"}
+USERNAME=${2:-"automatic"}
+
+# If in automatic mode, determine if a user already exists, if not use vscode
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=vscode
+ fi
+elif [ "${USERNAME}" = "none" ]; then
+ USERNAME=root
+ USER_UID=0
+ USER_GID=0
+fi
+
+# Use sudo to run as non-root user is not already running
+sudoUserIf()
+{
+ if [ "$(id -u)" -eq 0 ] && [ "${USERNAME}" != "root" ]; then
+ sudo -u ${USERNAME} "$@"
+ else
+ "$@"
+ fi
+}
+
+# If we don't yet have Python, install it now.
+if ! python --version > /dev/null ; then
+ echo "You need to install Python before installing JupyterLab."
+ exit 1
+fi
+
+# If we don't already have JupyterLab installed, install it now.
+if ! jupyter-lab --version > /dev/null ; then
+ echo "Installing JupyterLab..."
+ if [ "${VERSION}" = "latest" ]; then
+ pip install jupyterlab
+ else
+ pip install jupyterlab=="${VERSION}" --no-cache-dir
+ fi
+fi
diff --git a/collection/kubectl-helm-minikube/feature.json b/collection/kubectl-helm-minikube/feature.json
new file mode 100644
index 0000000..18723c7
--- /dev/null
+++ b/collection/kubectl-helm-minikube/feature.json
@@ -0,0 +1,38 @@
+{
+ "id": "kubectl-helm-minikube",
+ "name": "Kubectl, Helm, and Minkube",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "1.23", "1.22", "1.21"],
+ "default": "latest",
+ "description": "Select or enter a Kubernetes version to install"
+ },
+ "helm": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Select or enter a Helm version to install"
+ },
+ "minikube": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Select or enter a Minikube version to install"
+ }
+ },
+ "extensions": [
+ "ms-kubernetes-tools.vscode-kubernetes-tools"
+ ],
+ "mounts": [
+ {
+ "source":"minikube-config",
+ "target":"/home/vscode/.minikube",
+ "type":"volume"
+ }
+ ],
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/kubectl-helm-minikube/install.sh b/collection/kubectl-helm-minikube/install.sh
new file mode 100644
index 0000000..0981024
--- /dev/null
+++ b/collection/kubectl-helm-minikube/install.sh
@@ -0,0 +1,249 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/kubectl-helm.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./kubectl-helm-debian.sh [kubectl verison] [Helm version] [minikube version] [kubectl SHA256] [Helm SHA256] [minikube SHA256]
+
+set -e
+
+KUBECTL_VERSION="${1:-"latest"}"
+HELM_VERSION="${2:-"latest"}"
+MINIKUBE_VERSION="${3:-"none"}" # latest is also valid
+KUBECTL_SHA256="${4:-"automatic"}"
+HELM_SHA256="${5:-"automatic"}"
+MINIKUBE_SHA256="${6:-"automatic"}"
+USERNAME=${7:-"automatic"}
+
+HELM_GPG_KEYS_URI="https://raw.githubusercontent.com/helm/helm/main/KEYS"
+GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
+keyserver hkps://keys.openpgp.org
+keyserver hkp://keyserver.pgp.com"
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+USERHOME="/home/$USERNAME"
+if [ "$USERNAME" = "root" ]; then
+ USERHOME="/root"
+fi
+
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+# Install dependencies
+check_packages curl ca-certificates coreutils gnupg2 dirmngr bash-completion
+if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends git
+fi
+
+architecture="$(uname -m)"
+case $architecture in
+ x86_64) architecture="amd64";;
+ aarch64 | armv8*) architecture="arm64";;
+ aarch32 | armv7* | armvhf*) architecture="arm";;
+ i?86) architecture="386";;
+ *) echo "(!) Architecture $architecture unsupported"; exit 1 ;;
+esac
+
+# Install the kubectl, verify checksum
+echo "Downloading kubectl..."
+if [ "${KUBECTL_VERSION}" = "latest" ] || [ "${KUBECTL_VERSION}" = "lts" ] || [ "${KUBECTL_VERSION}" = "current" ] || [ "${KUBECTL_VERSION}" = "stable" ]; then
+ KUBECTL_VERSION="$(curl -sSL https://dl.k8s.io/release/stable.txt)"
+else
+ find_version_from_git_tags KUBECTL_VERSION https://github.com/kubernetes/kubernetes
+fi
+if [ "${KUBECTL_VERSION::1}" != 'v' ]; then
+ KUBECTL_VERSION="v${KUBECTL_VERSION}"
+fi
+curl -sSL -o /usr/local/bin/kubectl "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${architecture}/kubectl"
+chmod 0755 /usr/local/bin/kubectl
+if [ "$KUBECTL_SHA256" = "automatic" ]; then
+ KUBECTL_SHA256="$(curl -sSL "https://dl.k8s.io/${KUBECTL_VERSION}/bin/linux/${architecture}/kubectl.sha256")"
+fi
+([ "${KUBECTL_SHA256}" = "dev-mode" ] || (echo "${KUBECTL_SHA256} */usr/local/bin/kubectl" | sha256sum -c -))
+if ! type kubectl > /dev/null 2>&1; then
+ echo '(!) kubectl installation failed!'
+ exit 1
+fi
+
+# kubectl bash completion
+kubectl completion bash > /etc/bash_completion.d/kubectl
+
+# kubectl zsh completion
+if [ -e "${USERHOME}}/.oh-my-zsh" ]; then
+ mkdir -p "${USERHOME}/.oh-my-zsh/completions"
+ kubectl completion zsh > "${USERHOME}/.oh-my-zsh/completions/_kubectl"
+ chown -R "${USERNAME}" "${USERHOME}/.oh-my-zsh"
+fi
+
+# Install Helm, verify signature and checksum
+echo "Downloading Helm..."
+find_version_from_git_tags HELM_VERSION "https://github.com/helm/helm"
+if [ "${HELM_VERSION::1}" != 'v' ]; then
+ HELM_VERSION="v${HELM_VERSION}"
+fi
+mkdir -p /tmp/helm
+helm_filename="helm-${HELM_VERSION}-linux-${architecture}.tar.gz"
+tmp_helm_filename="/tmp/helm/${helm_filename}"
+curl -sSL "https://get.helm.sh/${helm_filename}" -o "${tmp_helm_filename}"
+curl -sSL "https://github.com/helm/helm/releases/download/${HELM_VERSION}/${helm_filename}.asc" -o "${tmp_helm_filename}.asc"
+export GNUPGHOME="/tmp/helm/gnupg"
+mkdir -p "${GNUPGHOME}"
+chmod 700 ${GNUPGHOME}
+get_common_setting HELM_GPG_KEYS_URI
+get_common_setting GPG_KEY_SERVERS true
+curl -sSL "${HELM_GPG_KEYS_URI}" -o /tmp/helm/KEYS
+echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
+gpg -q --import "/tmp/helm/KEYS"
+if ! gpg --verify "${tmp_helm_filename}.asc" > ${GNUPGHOME}/verify.log 2>&1; then
+ echo "Verification failed!"
+ cat /tmp/helm/gnupg/verify.log
+ exit 1
+fi
+if [ "${HELM_SHA256}" = "automatic" ]; then
+ curl -sSL "https://get.helm.sh/${helm_filename}.sha256" -o "${tmp_helm_filename}.sha256"
+ curl -sSL "https://github.com/helm/helm/releases/download/${HELM_VERSION}/${helm_filename}.sha256.asc" -o "${tmp_helm_filename}.sha256.asc"
+ if ! gpg --verify "${tmp_helm_filename}.sha256.asc" > /tmp/helm/gnupg/verify.log 2>&1; then
+ echo "Verification failed!"
+ cat /tmp/helm/gnupg/verify.log
+ exit 1
+ fi
+ HELM_SHA256="$(cat "${tmp_helm_filename}.sha256")"
+fi
+([ "${HELM_SHA256}" = "dev-mode" ] || (echo "${HELM_SHA256} *${tmp_helm_filename}" | sha256sum -c -))
+tar xf "${tmp_helm_filename}" -C /tmp/helm
+mv -f "/tmp/helm/linux-${architecture}/helm" /usr/local/bin/
+chmod 0755 /usr/local/bin/helm
+rm -rf /tmp/helm
+if ! type helm > /dev/null 2>&1; then
+ echo '(!) Helm installation failed!'
+ exit 1
+fi
+
+# Install Minikube, verify checksum
+if [ "${MINIKUBE_VERSION}" != "none" ]; then
+ echo "Downloading minikube..."
+ if [ "${MINIKUBE_VERSION}" = "latest" ] || [ "${MINIKUBE_VERSION}" = "lts" ] || [ "${MINIKUBE_VERSION}" = "current" ] || [ "${MINIKUBE_VERSION}" = "stable" ]; then
+ MINIKUBE_VERSION="latest"
+ else
+ find_version_from_git_tags MINIKUBE_VERSION https://github.com/kubernetes/minikube
+ if [ "${MINIKUBE_VERSION::1}" != "v" ]; then
+ MINIKUBE_VERSION="v${MINIKUBE_VERSION}"
+ fi
+ fi
+ # latest is also valid in the download URLs
+ curl -sSL -o /usr/local/bin/minikube "https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-${architecture}"
+ chmod 0755 /usr/local/bin/minikube
+ if [ "$MINIKUBE_SHA256" = "automatic" ]; then
+ MINIKUBE_SHA256="$(curl -sSL "https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-${architecture}.sha256")"
+ fi
+ ([ "${MINIKUBE_SHA256}" = "dev-mode" ] || (echo "${MINIKUBE_SHA256} */usr/local/bin/minikube" | sha256sum -c -))
+ if ! type minikube > /dev/null 2>&1; then
+ echo '(!) minikube installation failed!'
+ exit 1
+ fi
+ # Create minkube folder with correct privs in case a volume is mounted here
+ mkdir -p "${USERHOME}/.minikube"
+ chown -R $USERNAME "${USERHOME}/.minikube"
+ chmod -R u+wrx "${USERHOME}/.minikube"
+fi
+
+if ! type docker > /dev/null 2>&1; then
+ echo -e '\n(*) Warning: The docker command was not found.\n\nYou can use one of the following scripts to install it:\n\nhttps://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker-in-docker.md\n\nor\n\nhttps://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker.md'
+fi
+
+echo -e "\nDone!" \ No newline at end of file
diff --git a/collection/maven/feature.json b/collection/maven/feature.json
new file mode 100644
index 0000000..bc4f5f3
--- /dev/null
+++ b/collection/maven/feature.json
@@ -0,0 +1,22 @@
+{
+ "id": "maven",
+ "name": "Maven (via SDKMAN!)",
+ "documentationURL": "https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/maven.md",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "3.8", "3.6", "3.5"],
+ "default": "latest",
+ "description": "Select or enter a Maven version to install"
+ }
+ },
+ "extensions": ["vscjava.vscode-java-pack"],
+ "containerEnv": {
+ "SDKMAN_DIR": "/usr/local/sdkman",
+ "PATH": "${SDKMAN_DIR}/bin:${SDKMAN_DIR}/candidates/maven/current/bin:${PATH}"
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/maven/install.sh b/collection/maven/install.sh
new file mode 100644
index 0000000..f8369f5
--- /dev/null
+++ b/collection/maven/install.sh
@@ -0,0 +1,133 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/maven.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./maven-debian.sh [maven version] [SDKMAN_DIR] [non-root user] [Update rc files flag]
+
+MAVEN_VERSION=${1:-"latest"}
+export SDKMAN_DIR=${2:-"/usr/local/sdkman"}
+USERNAME=${3:-"automatic"}
+UPDATE_RC=${4:-"true"}
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Use SDKMAN to install something using a partial version match
+sdk_install() {
+ local install_type=$1
+ local requested_version=$2
+ local prefix=$3
+ local suffix="${4:-"\\s*"}"
+ local full_version_check=${5:-".*-[a-z]+"}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ # Blank will install latest stable version
+ if [ "${requested_version}" = "lts" ] || [ "${requested_version}" = "default" ]; then
+ requested_version=""
+ elif echo "${requested_version}" | grep -oE "${full_version_check}" > /dev/null 2>&1; then
+ echo "${requested_version}"
+ else
+ local regex="${prefix}\\K[0-9]+\\.[0-9]+\\.[0-9]+${suffix}"
+ local version_list="$(. ${SDKMAN_DIR}/bin/sdkman-init.sh && sdk list ${install_type} 2>&1 | grep -oP "${regex}" | tr -d ' ' | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ]; then
+ requested_version="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ requested_version="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ if [ -z "${requested_version}" ] || ! echo "${version_list}" | grep "^${requested_version//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Version $2 not found. Available versions:\n${version_list}" >&2
+ exit 1
+ fi
+ fi
+ su ${USERNAME} -c "umask 0002 && . ${SDKMAN_DIR}/bin/sdkman-init.sh && sdk install ${install_type} ${requested_version} && sdk flush archives && sdk flush temp"
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Install dependencies
+check_packages curl ca-certificates zip unzip sed
+
+# Install sdkman if not installed
+if [ ! -d "${SDKMAN_DIR}" ]; then
+ # Create sdkman group, dir, and set sticky bit
+ if ! cat /etc/group | grep -e "^sdkman:" > /dev/null 2>&1; then
+ groupadd -r sdkman
+ fi
+ usermod -a -G sdkman ${USERNAME}
+ umask 0002
+ # Install SDKMAN
+ curl -sSL "https://get.sdkman.io?rcupdate=false" | bash
+ chown -R :sdkman ${SDKMAN_DIR}
+ find ${SDKMAN_DIR} -type d | xargs -d '\n' chmod g+s
+ # Add sourcing of sdkman into bashrc/zshrc files (unless disabled)
+ updaterc "export SDKMAN_DIR=${SDKMAN_DIR}\n. \${SDKMAN_DIR}/bin/sdkman-init.sh"
+fi
+
+# Install Maven
+sdk_install maven ${MAVEN_VERSION} '\s\s' '\s\s' '^[0-9]+\.[0-9]+\.[0-9]+$'
+updaterc '[ -z "$M2" ] && export M2=$HOME/.m2'
+
+echo "Done!" \ No newline at end of file
diff --git a/collection/node/feature.json b/collection/node/feature.json
new file mode 100644
index 0000000..5a8e81d
--- /dev/null
+++ b/collection/node/feature.json
@@ -0,0 +1,27 @@
+{
+ "id": "node",
+ "name": "Node.js (via nvm) and yarn",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": [ "lts", "latest", "18", "16", "14" ],
+ "default": "lts",
+ "description": "Select or enter a Node.js version to install"
+ },
+ "nodeGypDependencies": {
+ "type": "boolean",
+ "default": true,
+ "description": "Install dependencies to compile native node modules (node-gyp)?"
+ }
+ },
+ "extensions": ["dbaeumer.vscode-eslint"],
+ "containerEnv": {
+ "NVM_DIR":"/usr/local/share/nvm",
+ "NVM_SYMLINK_CURRENT": "true",
+ "PATH": "${NVM_DIR}/current/bin:${PATH}"
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/node/install.sh b/collection/node/install.sh
new file mode 100644
index 0000000..c355168
--- /dev/null
+++ b/collection/node/install.sh
@@ -0,0 +1,169 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/node.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./node-debian.sh [directory to install nvm] [node version to install (use "none" to skip)] [non-root user] [Update rc files flag] [install node-gyp deps]
+
+export NVM_DIR=${1:-"/usr/local/share/nvm"}
+export NODE_VERSION=${2:-"lts"}
+USERNAME=${3:-"automatic"}
+UPDATE_RC=${4:-"true"}
+INSTALL_TOOLS_FOR_NODE_GYP="${5:-true}"
+export NVM_VERSION="0.38.0"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+# Install dependencies
+check_packages apt-transport-https curl ca-certificates tar gnupg2 dirmngr
+
+# Install yarn
+if type yarn > /dev/null 2>&1; then
+ echo "Yarn already installed."
+else
+ # Import key safely (new method rather than deprecated apt-key approach) and install
+ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
+ apt-get update
+ apt-get -y install --no-install-recommends yarn
+fi
+
+# Adjust node version if required
+if [ "${NODE_VERSION}" = "none" ]; then
+ export NODE_VERSION=
+elif [ "${NODE_VERSION}" = "lts" ]; then
+ export NODE_VERSION="lts/*"
+fi
+
+# Create a symlink to the installed version for use in Dockerfile PATH statements
+export NVM_SYMLINK_CURRENT=true
+
+# Install the specified node version if NVM directory already exists, then exit
+if [ -d "${NVM_DIR}" ]; then
+ echo "NVM already installed."
+ if [ "${NODE_VERSION}" != "" ]; then
+ su ${USERNAME} -c ". $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm clear-cache"
+ fi
+ exit 0
+fi
+
+# Create nvm group, nvm dir, and set sticky bit
+if ! cat /etc/group | grep -e "^nvm:" > /dev/null 2>&1; then
+ groupadd -r nvm
+fi
+umask 0002
+usermod -a -G nvm ${USERNAME}
+mkdir -p ${NVM_DIR}
+chown :nvm ${NVM_DIR}
+chmod g+s ${NVM_DIR}
+su ${USERNAME} -c "$(cat << EOF
+ set -e
+ umask 0002
+ # Do not update profile - we'll do this manually
+ export PROFILE=/dev/null
+ curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash
+ source ${NVM_DIR}/nvm.sh
+ if [ "${NODE_VERSION}" != "" ]; then
+ nvm alias default ${NODE_VERSION}
+ fi
+ nvm clear-cache
+EOF
+)" 2>&1
+# Update rc files
+if [ "${UPDATE_RC}" = "true" ]; then
+updaterc "$(cat <<EOF
+export NVM_DIR="${NVM_DIR}"
+[ -s "\$NVM_DIR/nvm.sh" ] && . "\$NVM_DIR/nvm.sh"
+[ -s "\$NVM_DIR/bash_completion" ] && . "\$NVM_DIR/bash_completion"
+EOF
+)"
+fi
+
+# If enabled, verify "python3", "make", "gcc", "g++" commands are available so node-gyp works - https://github.com/nodejs/node-gyp
+if [ "${INSTALL_TOOLS_FOR_NODE_GYP}" = "true" ]; then
+ echo "Verifying node-gyp OS requirements..."
+ to_install=""
+ if ! type make > /dev/null 2>&1; then
+ to_install="${to_install} make"
+ fi
+ if ! type gcc > /dev/null 2>&1; then
+ to_install="${to_install} gcc"
+ fi
+ if ! type g++ > /dev/null 2>&1; then
+ to_install="${to_install} g++"
+ fi
+ if ! type python3 > /dev/null 2>&1; then
+ to_install="${to_install} python3-minimal"
+ fi
+ if [ ! -z "${to_install}" ]; then
+ apt_get_update_if_needed
+ apt-get -y install ${to_install}
+ fi
+fi
+
+echo "Done!"
diff --git a/collection/powershell/feature.json b/collection/powershell/feature.json
new file mode 100644
index 0000000..d81bb4b
--- /dev/null
+++ b/collection/powershell/feature.json
@@ -0,0 +1,16 @@
+{
+ "id": "powershell",
+ "name": "PowerShell",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "7.1"],
+ "default": "latest",
+ "description": "Select or enter a version of PowerShell."
+ }
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/powershell/install.sh b/collection/powershell/install.sh
new file mode 100644
index 0000000..3110356
--- /dev/null
+++ b/collection/powershell/install.sh
@@ -0,0 +1,170 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/powershell.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./powershell-debian.sh [version]
+
+set -e
+
+POWERSHELL_VERSION=${1:-"latest"}
+MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
+POWERSHELL_ARCHIVE_ARCHITECTURES="amd64"
+POWERSHELL_ARCHIVE_VERSION_CODENAMES="stretch buster bionic focal"
+GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
+keyserver hkps://keys.openpgp.org
+keyserver hkp://keyserver.pgp.com"
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+install_using_apt() {
+ # Install dependencies
+ check_packages apt-transport-https curl ca-certificates gnupg2 dirmngr
+ # Import key safely (new 'signed-by' method rather than deprecated apt-key approach) and install
+ get_common_setting MICROSOFT_GPG_KEYS_URI
+ curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list
+
+ # Update lists
+ apt-get update -yq
+
+ # Soft version matching for CLI
+ if [ "${POWERSHELL_VERSION}" = "latest" ] || [ "${POWERSHELL_VERSION}" = "lts" ] || [ "${POWERSHELL_VERSION}" = "stable" ]; then
+ # Empty, meaning grab whatever "latest" is in apt repo
+ version_suffix=""
+ else
+ version_suffix="=$(apt-cache madison powershell | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "^(${POWERSHELL_VERSION})(\.|$|\+.*|-.*)")"
+
+ if [ -z ${version_suffix} ] || [ ${version_suffix} = "=" ]; then
+ echo "Provided POWERSHELL_VERSION (${POWERSHELL_VERSION}) was not found in the apt-cache for this package+distribution combo";
+ return 1
+ fi
+ echo "version_suffix ${version_suffix}"
+ fi
+
+ apt-get install -yq powershell${version_suffix} || return 1
+}
+
+install_using_github() {
+ # Fall back on direct download if no apt package exists in microsoft pool
+ check_packages curl ca-certificates gnupg2 dirmngr libc6 libgcc1 libgssapi-krb5-2 liblttng-ust0 libstdc++6 libunwind8 libuuid1 zlib1g libicu[0-9][0-9]
+ if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get install -y --no-install-recommends git
+ fi
+ if [ "${architecture}" = "amd64" ]; then
+ architecture="x64"
+ fi
+ find_version_from_git_tags POWERSHELL_VERSION https://github.com/PowerShell/PowerShell
+ powershell_filename="powershell-${POWERSHELL_VERSION}-linux-${architecture}.tar.gz"
+ powershell_target_path="/opt/microsoft/powershell/$(echo ${POWERSHELL_VERSION} | grep -oE '[^\.]+' | head -n 1)"
+ mkdir -p /tmp/pwsh "${powershell_target_path}"
+ cd /tmp/pwsh
+ curl -sSL -o "${powershell_filename}" "https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/${powershell_filename}"
+ # Ugly - but only way to get sha256 is to parse release HTML. Remove newlines and tags, then look for filename followed by 64 hex characters.
+ curl -sSL -o "release.html" "https://github.com/PowerShell/PowerShell/releases/tag/v${POWERSHELL_VERSION}"
+ powershell_archive_sha256="$(cat release.html | tr '\n' ' ' | sed 's|<[^>]*>||g' | grep -oP "${powershell_filename}\s+\K[0-9a-fA-F]{64}" || echo '')"
+ if [ -z "${powershell_archive_sha256}" ]; then
+ echo "(!) WARNING: Failed to retrieve SHA256 for archive. Skipping validaiton."
+ else
+ echo "SHA256: ${powershell_archive_sha256}"
+ echo "${powershell_archive_sha256} *${powershell_filename}" | sha256sum -c -
+ fi
+ tar xf "${powershell_filename}" -C "${powershell_target_path}"
+ ln -s "${powershell_target_path}/pwsh" /usr/local/bin/pwsh
+ rm -rf /tmp/pwsh
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Source /etc/os-release to get OS info
+. /etc/os-release
+architecture="$(dpkg --print-architecture)"
+
+if [[ "${POWERSHELL_ARCHIVE_ARCHITECTURES}" = *"${architecture}"* ]] && [[ "${POWERSHELL_ARCHIVE_VERSION_CODENAMES}" = *"${VERSION_CODENAME}"* ]]; then
+ install_using_apt || use_github="true"
+else
+ use_github="true"
+fi
+
+if [ "${use_github}" = "true" ]; then
+ echo "Attempting install from GitHub release..."
+ install_using_github
+fi
+
+echo "Done!" \ No newline at end of file
diff --git a/collection/python/feature.json b/collection/python/feature.json
new file mode 100644
index 0000000..18728f3
--- /dev/null
+++ b/collection/python/feature.json
@@ -0,0 +1,56 @@
+{
+ "id": "python",
+ "name": "Python (may require compilation)",
+ "description": "Python (may require compilation)",
+ "options": {
+ "version": {
+ "type": "string",
+ "enum": ["latest", "os-provided", "3.10", "3.9", "3.8", "3.7", "3.6"],
+ "default": "os-provided",
+ "description": "Select a Python version to install."
+ },
+ "installTools": {
+ "type": "boolean",
+ "default": true,
+ "description": "Install common Python tools like pylint"
+ },
+ "optimize": {
+ "type": "boolean",
+ "default": false,
+ "description": "Optimize Python for performance when compiled (slow)"
+ },
+ "install_path": {
+ "type": "string",
+ "default": "/usr/local/python",
+ "description": "The path where python will be installed."
+ },
+ "setup_links": {
+ "type": "boolean",
+ "default": "true",
+ "description": "If links and aliases should be created."
+ }
+ },
+ "containerEnv": {
+ "PYTHON_PATH": "/usr/local/python",
+ "PIPX_HOME": "/usr/local/py-utils",
+ "PIPX_BIN_DIR": "/usr/local/py-utils/bin",
+ "PATH": "${PYTHON_PATH}/bin:${PATH}:${PIPX_BIN_DIR}"
+ },
+ "extensions": ["ms-python.python", "ms-python.vscode-pylance"],
+ "settings": {
+ "python.defaultInterpreterPath": "/usr/local/bin/python",
+ "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
+ "python.formatting.blackPath": "/usr/local/py-utils/bin/black",
+ "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
+ "python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
+ "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
+ "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
+ "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
+ "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
+ "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+}
diff --git a/collection/python/python-debian.sh b/collection/python/python-debian.sh
new file mode 100644
index 0000000..3a2d429
--- /dev/null
+++ b/collection/python/python-debian.sh
@@ -0,0 +1,376 @@
+#!/usr/bin/env bash
+
+PYTHON_VERSION=($PYTHON_VERSION) # 'system' checks the base image first, else installs 'latest'
+PYTHON_INSTALL_PATH=($PYTHON_INSTALL_PATH)
+PYTHON_SETUP_LINKS=($PYTHON_SETUP_LINKS)
+
+#PYTHON_VERSION=${1:-"latest"} # 'system' checks the base image first, else installs 'latest'
+#PYTHON_INSTALL_PATH=${2:-"/usr/local/python"}
+export PIPX_HOME=${3:-"/usr/local/py-utils"}
+USERNAME=${4:-"automatic"}
+UPDATE_RC=${5:-"true"}
+INSTALL_PYTHON_TOOLS=${6:-"true"}
+USE_ORYX_IF_AVAILABLE=${7:-"true"}
+OPTIMIZE_BUILD_FROM_SOURCE=${8-"false"}
+
+DEFAULT_UTILS=("pylint" "flake8" "autopep8" "black" "yapf" "mypy" "pydocstyle" "pycodestyle" "bandit" "pipenv" "virtualenv")
+PYTHON_SOURCE_GPG_KEYS="64E628F8D684696D B26995E310250568 2D347EA6AA65421D FB9921286F5E1540 3A5CA953F73C700D 04C367C218ADD4FF 0EDDC5F26A45C816 6AF053F07D9DC8D2 C9BE28DEE6DF025C 126EB563A74B06BF D9866941EA5BBD71 ED9D77D5"
+GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
+keyserver hkps://keys.openpgp.org
+keyserver hkp://keyserver.pgp.com"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Import the specified key in a variable name passed in as
+receive_gpg_keys() {
+ get_common_setting $1
+ local keys=${!1}
+ get_common_setting GPG_KEY_SERVERS true
+ local keyring_args=""
+ if [ ! -z "$2" ]; then
+ mkdir -p "$(dirname \"$2\")"
+ keyring_args="--no-default-keyring --keyring $2"
+ fi
+
+ # Use a temporary locaiton for gpg keys to avoid polluting image
+ export GNUPGHOME="/tmp/tmp-gnupg"
+ mkdir -p ${GNUPGHOME}
+ chmod 700 ${GNUPGHOME}
+ echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
+ # GPG key download sometimes fails for some reason and retrying fixes it.
+ local retry_count=0
+ local gpg_ok="false"
+ set +e
+ until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
+ do
+ echo "(*) Downloading GPG key..."
+ ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true"
+ if [ "${gpg_ok}" != "true" ]; then
+ echo "(*) Failed getting key, retring in 10s..."
+ (( retry_count++ ))
+ sleep 10s
+ fi
+ done
+ set -e
+ if [ "${gpg_ok}" = "false" ]; then
+ echo "(!) Failed to get gpg key."
+ exit 1
+ fi
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Use Oryx to install something using a partial version match
+oryx_install() {
+ local platform=$1
+ local requested_version=$2
+ local target_folder=${3:-none}
+ local ldconfig_folder=${4:-none}
+ echo "(*) Installing ${platform} ${requested_version} using Oryx..."
+ check_packages jq
+ # Soft match if full version not specified
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local version_list="$(oryx platforms --json | jq -r ".[] | select(.Name == \"${platform}\") | .Versions | sort | reverse | @tsv" | tr '\t' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$')"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ requested_version="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ requested_version="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ if [ -z "${requested_version}" ] || ! echo "${version_list}" | grep "^${requested_version//./\\.}$" > /dev/null 2>&1; then
+ echo -e "(!) Oryx does not support ${platform} version $2\nValid values:\n${version_list}" >&2
+ return 1
+ fi
+ echo "(*) Using ${requested_version} in place of $2."
+ fi
+
+ export ORYX_ENV_TYPE=vsonline-present ORYX_PREFER_USER_INSTALLED_SDKS=true ENABLE_DYNAMIC_INSTALL=true DYNAMIC_INSTALL_ROOT_DIR=/opt
+ oryx prep --skip-detection --platforms-and-versions "${platform}=${requested_version}"
+ local opt_folder="/opt/${platform}/${requested_version}"
+ if [ "${target_folder}" != "none" ] && [ "${target_folder}" != "${opt_folder}" ]; then
+ ln -s "${opt_folder}" "${target_folder}"
+ fi
+ # Update library path add to conf
+ if [ "${ldconfig_folder}" != "none" ]; then
+ echo "/opt/${platform}/${requested_version}/lib" >> "/etc/ld.so.conf.d/${platform}.conf"
+ ldconfig
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+install_from_source() {
+ if [ -d "${PYTHON_INSTALL_PATH}" ]; then
+ echo "(!) Path ${PYTHON_INSTALL_PATH} already exists. Remove this existing path or select a different one."
+ exit 1
+ fi
+ echo "(*) Building Python ${PYTHON_VERSION} from source..."
+ # Install prereqs if missing
+ check_packages curl ca-certificates gnupg2 tar make gcc libssl-dev zlib1g-dev libncurses5-dev \
+ libbz2-dev libreadline-dev libxml2-dev xz-utils libgdbm-dev tk-dev dirmngr \
+ libxmlsec1-dev libsqlite3-dev libffi-dev liblzma-dev uuid-dev
+ if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends git
+ fi
+
+ # Find version using soft match
+ find_version_from_git_tags PYTHON_VERSION "https://github.com/python/cpython"
+
+ # Download tgz of source
+ mkdir -p /tmp/python-src "${PYTHON_INSTALL_PATH}"
+ cd /tmp/python-src
+ local tgz_filename="Python-${PYTHON_VERSION}.tgz"
+ local tgz_url="https://www.python.org/ftp/python/${PYTHON_VERSION}/${tgz_filename}"
+ echo "Downloading ${tgz_filename}..."
+ curl -sSL -o "/tmp/python-src/${tgz_filename}" "${tgz_url}"
+
+ # Verify signature
+ receive_gpg_keys PYTHON_SOURCE_GPG_KEYS
+ echo "Downloading ${tgz_filename}.asc..."
+ curl -sSL -o "/tmp/python-src/${tgz_filename}.asc" "${tgz_url}.asc"
+ gpg --verify "${tgz_filename}.asc"
+
+ # Update min protocol for testing only - https://bugs.python.org/issue41561
+ cp /etc/ssl/openssl.cnf /tmp/python-src/
+ sed -i -E 's/MinProtocol[=\ ]+.*/MinProtocol = TLSv1.0/g' /tmp/python-src/openssl.cnf
+ export OPENSSL_CONF=/tmp/python-src/openssl.cnf
+
+ # Untar and build
+ tar -xzf "/tmp/python-src/${tgz_filename}" -C "/tmp/python-src" --strip-components=1
+ local config_args=""
+ if [ "${OPTIMIZE_BUILD_FROM_SOURCE}" = "true" ]; then
+ config_args="--enable-optimizations"
+ fi
+ ./configure --prefix="${PYTHON_INSTALL_PATH}" --with-ensurepip=install ${config_args}
+ make -j 8
+ make install
+ cd /tmp
+ rm -rf /tmp/python-src ${GNUPGHOME} /tmp/vscdc-settings.env
+ chown -R ${USERNAME} "${PYTHON_INSTALL_PATH}"
+ ln -s ${PYTHON_INSTALL_PATH}/bin/python3 ${PYTHON_INSTALL_PATH}/bin/python
+ ln -s ${PYTHON_INSTALL_PATH}/bin/pip3 ${PYTHON_INSTALL_PATH}/bin/pip
+ ln -s ${PYTHON_INSTALL_PATH}/bin/idle3 ${PYTHON_INSTALL_PATH}/bin/idle
+ ln -s ${PYTHON_INSTALL_PATH}/bin/pydoc3 ${PYTHON_INSTALL_PATH}/bin/pydoc
+ ln -s ${PYTHON_INSTALL_PATH}/bin/python3-config ${PYTHON_INSTALL_PATH}/bin/python-config
+}
+
+install_using_oryx() {
+ if [ -d "${PYTHON_INSTALL_PATH}" ]; then
+ echo "(!) Path ${PYTHON_INSTALL_PATH} already exists. Remove this existing path or select a different one."
+ exit 1
+ fi
+ oryx_install "python" "${PYTHON_VERSION}" "${PYTHON_INSTALL_PATH}" "lib" || return 1
+ ln -s ${PYTHON_INSTALL_PATH}/bin/idle3 ${PYTHON_INSTALL_PATH}/bin/idle
+ ln -s ${PYTHON_INSTALL_PATH}/bin/pydoc3 ${PYTHON_INSTALL_PATH}/bin/pydoc
+ ln -s ${PYTHON_INSTALL_PATH}/bin/python3-config ${PYTHON_INSTALL_PATH}/bin/python-config
+}
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+# General requirements
+check_packages curl ca-certificates gnupg2 tar make gcc libssl-dev zlib1g-dev libncurses5-dev \
+ libbz2-dev libreadline-dev libxml2-dev xz-utils libgdbm-dev tk-dev dirmngr \
+ libxmlsec1-dev libsqlite3-dev libffi-dev liblzma-dev uuid-dev
+
+
+# Install python from source if needed
+if [ "${PYTHON_VERSION}" != "none" ]; then
+ # If the os-provided versions are "good enough", detect that and bail out.
+ if [ ${PYTHON_VERSION} = "os-provided" ] || [ ${PYTHON_VERSION} = "system" ]; then
+ check_packages python3 python3-doc python3-pip python3-venv python3-dev python3-tk
+ PYTHON_INSTALL_PATH="/usr"
+ should_install_from_source=false
+ elif [ "$(dpkg --print-architecture)" = "amd64" ] && [ "${USE_ORYX_IF_AVAILABLE}" = "true" ] && type oryx > /dev/null 2>&1; then
+ install_using_oryx || should_install_from_source=true
+ else
+ should_install_from_source=true
+ fi
+ if [ "${should_install_from_source}" = "true" ]; then
+ install_from_source
+ fi
+ updaterc "if [[ \"\${PATH}\" != *\"${PYTHON_INSTALL_PATH}/bin\"* ]]; then export PATH=${PYTHON_INSTALL_PATH}/bin:\${PATH}; fi"
+fi
+
+# If not installing python tools, exit
+if [ "${INSTALL_PYTHON_TOOLS}" != "true" ]; then
+ echo "Done!"
+ exit 0
+fi
+
+export PIPX_BIN_DIR="${PIPX_HOME}/bin"
+export PATH="${PYTHON_INSTALL_PATH}/bin:${PIPX_BIN_DIR}:${PATH}"
+
+# Create pipx group, dir, and set sticky bit
+if ! cat /etc/group | grep -e "^pipx:" > /dev/null 2>&1; then
+ groupadd -r pipx
+fi
+usermod -a -G pipx ${USERNAME}
+umask 0002
+mkdir -p ${PIPX_BIN_DIR}
+chown :pipx ${PIPX_HOME} ${PIPX_BIN_DIR}
+chmod g+s ${PIPX_HOME} ${PIPX_BIN_DIR}
+
+# Update pip if not using os provided python
+if [ ${PYTHON_VERSION} != "os-provided" ] && [ ${PYTHON_VERSION} != "system" ]; then
+ echo "Updating pip..."
+ ${PYTHON_INSTALL_PATH}/bin/python3 -m pip install --no-cache-dir --upgrade pip
+fi
+
+# Install tools
+echo "Installing Python tools..."
+export PYTHONUSERBASE=/tmp/pip-tmp
+export PIP_CACHE_DIR=/tmp/pip-tmp/cache
+pipx_path=""
+if ! type pipx > /dev/null 2>&1; then
+ pip3 install --disable-pip-version-check --no-cache-dir --user pipx 2>&1
+ /tmp/pip-tmp/bin/pipx install --pip-args=--no-cache-dir pipx
+ pipx_path="/tmp/pip-tmp/bin/"
+fi
+for util in ${DEFAULT_UTILS[@]}; do
+ if ! type ${util} > /dev/null 2>&1; then
+ ${pipx_path}pipx install --system-site-packages --pip-args '--no-cache-dir --force-reinstall' ${util}
+ else
+ echo "${util} already installed. Skipping."
+ fi
+done
+rm -rf /tmp/pip-tmp
+
+echo "(*) Updating RC files."
+
+updaterc "$(cat << EOF
+export PIPX_HOME="${PIPX_HOME}"
+export PIPX_BIN_DIR="${PIPX_BIN_DIR}"
+if [[ "\${PATH}" != *"\${PIPX_BIN_DIR}"* ]]; then export PATH="\${PATH}:\${PIPX_BIN_DIR}"; fi
+EOF
+)"
+
+echo "(*) About to do final Python setup."
+
+INSTALLED_PYTHON_VERSION=$(${PYTHON_INSTALL_PATH}/bin/python --version 2>&1 | grep -Po '(?<=Python )(.+)')
+if [[ ! -z "$INSTALLED_PYTHON_VERSION" ]] ; then
+ echo "(*) Installed Python version is ${INSTALLED_PYTHON_VERSION}"
+ PYTHON_MAJOR_VERSION=$(echo ${INSTALLED_PYTHON_VERSION} | cut -f1 -d'.')
+ PYTHON_MINOR_VERSION=$(echo ${INSTALLED_PYTHON_VERSION} | cut -f2 -d'.')
+
+ if [ "${PYTHON_SETUP_LINKS}" = "true" ]; then
+ mkdir -p /opt/python
+ ln -s /usr/share/python /opt/python/${INSTALLED_PYTHON_VERSION}
+ ln -s /opt/python/${INSTALLED_PYTHON_VERSION} /opt/python/${PYTHON_MAJOR_VERSION}.${PYTHON_MINOR_VERSION}
+ ln -s /opt/python/${PYTHON_MAJOR_VERSION}.${PYTHON_MINOR_VERSION} /opt/python/${PYTHON_MAJOR_VERSION}
+ ln -s /opt/python/${INSTALLED_PYTHON_VERSION} /opt/python/latest
+ ln -s /opt/python/${INSTALLED_PYTHON_VERSION} /opt/python/stable
+
+ mkdir -p /home/${USERNAME}/.python
+ ln -s /opt/python/${INSTALLED_PYTHON_VERSION} /home/${USERNAME}/.python/current
+ fi
+else
+ echo "(*) Failed to get installed Python version."
+fi
+
+echo "(*) Done with Python feature."
diff --git a/collection/ruby/feature.json b/collection/ruby/feature.json
new file mode 100644
index 0000000..3ba0a36
--- /dev/null
+++ b/collection/ruby/feature.json
@@ -0,0 +1,25 @@
+{
+ "id": "ruby",
+ "name": "Ruby (via rvm)",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "3.1", "3.0", "2.7"],
+ "default": "latest",
+ "description": "Select or enter a Ruby version to install"
+ }
+ },
+ "extensions": [
+ "rebornix.Ruby"
+ ],
+ "containerEnv": {
+ "GEM_PATH": "/usr/local/rvm/gems/default:/usr/local/rvm/gems/default@global",
+ "GEM_HOME": "/usr/local/rvm/gems/default",
+ "MY_RUBY_HOME": "/usr/local/rvm/rubies/default",
+ "PATH": "/usr/local/rvm/gems/default/bin:/usr/local/rvm/gems/default@global/bin:/usr/local/rvm/rubies/default/bin:${PATH}"
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/ruby/install.sh b/collection/ruby/install.sh
new file mode 100644
index 0000000..9908510
--- /dev/null
+++ b/collection/ruby/install.sh
@@ -0,0 +1,271 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/ruby.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./ruby-debian.sh [Ruby version] [non-root user] [Add to rc files flag] [Install tools flag]
+
+RUBY_VERSION=${1:-"latest"}
+USERNAME=${2:-"automatic"}
+UPDATE_RC=${3:-"true"}
+INSTALL_RUBY_TOOLS=${6:-"true"}
+
+# Note: ruby-debug-ide will install the right version of debase if missing and
+# installing debase directly fails on Ruby 3.1.0 as of 1/7/2022, so omitting.
+DEFAULT_GEMS="rake ruby-debug-ide"
+
+RVM_GPG_KEYS="409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB"
+GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
+keyserver hkps://keys.openpgp.org
+keyserver hkp://keyserver.pgp.com"
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Import the specified key in a variable name passed in as
+receive_gpg_keys() {
+ get_common_setting $1
+ local keys=${!1}
+ get_common_setting GPG_KEY_SERVERS true
+ local keyring_args=""
+ if [ ! -z "$2" ]; then
+ keyring_args="--no-default-keyring --keyring \"$2\""
+ fi
+
+ # Use a temporary locaiton for gpg keys to avoid polluting image
+ export GNUPGHOME="/tmp/tmp-gnupg"
+ mkdir -p ${GNUPGHOME}
+ chmod 700 ${GNUPGHOME}
+ echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
+ # GPG key download sometimes fails for some reason and retrying fixes it.
+ local retry_count=0
+ local gpg_ok="false"
+ set +e
+ until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
+ do
+ echo "(*) Downloading GPG key..."
+ ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true"
+ if [ "${gpg_ok}" != "true" ]; then
+ echo "(*) Failed getting key, retring in 10s..."
+ (( retry_count++ ))
+ sleep 10s
+ fi
+ done
+ set -e
+ if [ "${gpg_ok}" = "false" ]; then
+ echo "(!) Failed to get gpg key."
+ exit 1
+ fi
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+architecture="$(uname -m)"
+if [ "${architecture}" != "amd64" ] && [ "${architecture}" != "x86_64" ] && [ "${architecture}" != "arm64" ] && [ "${architecture}" != "aarch64" ]; then
+ echo "(!) Architecture $architecture unsupported"
+ exit 1
+fi
+
+# Install dependencies
+check_packages curl ca-certificates software-properties-common build-essential gnupg2 libreadline-dev \
+ procps dirmngr gawk autoconf automake bison libffi-dev libgdbm-dev libncurses5-dev \
+ libsqlite3-dev libtool libyaml-dev pkg-config sqlite3 zlib1g-dev libgmp-dev libssl-dev
+if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends git
+fi
+
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags RUBY_VERSION "https://github.com/ruby/ruby" "tags/v" "_"
+
+# Just install Ruby if RVM already installed
+if [ -d "/usr/local/rvm" ]; then
+ echo "Ruby Version Manager already exists."
+ if [ "${RUBY_VERSION}" != "none" ]; then
+ echo "Installing specified Ruby version."
+ su ${USERNAME} -c "&& rvm install ruby ${RUBY_VERSION}"
+ fi
+ SKIP_GEM_INSTALL="false"
+else
+ # Install RVM
+ receive_gpg_keys RVM_GPG_KEYS
+ # Determine appropriate settings for rvm installer
+ if [ "${RUBY_VERSION}" = "none" ]; then
+ RVM_INSTALL_ARGS=""
+ else
+ if [ "${RUBY_VERSION}" = "latest" ] || [ "${RUBY_VERSION}" = "current" ] || [ "${RUBY_VERSION}" = "lts" ]; then
+ RVM_INSTALL_ARGS="--ruby"
+ RUBY_VERSION=""
+ else
+ RVM_INSTALL_ARGS="--ruby=${RUBY_VERSION}"
+ fi
+ if [ "${INSTALL_RUBY_TOOLS}" = "true" ]; then
+ SKIP_GEM_INSTALL="true"
+ else
+ DEFAULT_GEMS=""
+ fi
+ fi
+ # Create rvm group as a system group to reduce the odds of conflict with local user UIDs
+ if ! cat /etc/group | grep -e "^rvm:" > /dev/null 2>&1; then
+ groupadd -r rvm
+ fi
+ # Install rvm
+ curl -sSL https://get.rvm.io | bash -s stable --ignore-dotfiles ${RVM_INSTALL_ARGS} --with-default-gems="${DEFAULT_GEMS}" 2>&1
+ usermod -aG rvm ${USERNAME}
+ su ${USERNAME} -c ". /usr/local/rvm/scripts/rvm && rvm fix-permissions system"
+ rm -rf ${GNUPGHOME}
+fi
+
+if [ "${INSTALL_RUBY_TOOLS}" = "true" ]; then
+ # Non-root user may not have "gem" in path when script is run and no ruby version
+ # is installed by rvm, so handle this by using root's default gem in this case
+ ROOT_GEM='$(which gem || echo "")'
+ su ${USERNAME} -c ". /usr/local/rvm/scripts/rvm && \"$(which gem || echo ${ROOT_GEM})\" install ${DEFAULT_GEMS}"
+fi
+
+# VS Code server usually first in the path, so silence annoying rvm warning (that does not apply) and then source it
+updaterc "if ! grep rvm_silence_path_mismatch_check_flag \$HOME/.rvmrc > /dev/null 2>&1; then echo 'rvm_silence_path_mismatch_check_flag=1' >> \$HOME/.rvmrc; fi\nsource /usr/local/rvm/scripts/rvm > /dev/null 2>&1"
+
+# Install rbenv/ruby-build for good measure
+git clone --depth=1 \
+ -c core.eol=lf \
+ -c core.autocrlf=false \
+ -c fsck.zeroPaddedFilemode=ignore \
+ -c fetch.fsck.zeroPaddedFilemode=ignore \
+ -c receive.fsck.zeroPaddedFilemode=ignore \
+ https://github.com/rbenv/rbenv.git /usr/local/share/rbenv
+ln -s /usr/local/share/rbenv/bin/rbenv /usr/local/bin
+updaterc 'eval "$(rbenv init -)"'
+git clone --depth=1 \
+ -c core.eol=lf \
+ -c core.autocrlf=false \
+ -c fsck.zeroPaddedFilemode=ignore \
+ -c fetch.fsck.zeroPaddedFilemode=ignore \
+ -c receive.fsck.zeroPaddedFilemode=ignore \
+ https://github.com/rbenv/ruby-build.git /usr/local/share/ruby-build
+mkdir -p /root/.rbenv/plugins
+ln -s /usr/local/share/ruby-build /root/.rbenv/plugins/ruby-build
+if [ "${USERNAME}" != "root" ]; then
+ mkdir -p /home/${USERNAME}/.rbenv/plugins
+ chown -R ${USERNAME} /home/${USERNAME}/.rbenv
+ ln -s /usr/local/share/ruby-build /home/${USERNAME}/.rbenv/plugins/ruby-build
+fi
+
+# Clean up
+source /usr/local/rvm/scripts/rvm
+rvm cleanup all
+gem cleanup
+echo "Done!"
diff --git a/collection/rust/feature.json b/collection/rust/feature.json
new file mode 100644
index 0000000..47df7e1
--- /dev/null
+++ b/collection/rust/feature.json
@@ -0,0 +1,43 @@
+{
+ "id": "rust",
+ "name": "Rust",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "1.55", "1.54", "1.53"],
+ "default": "latest",
+ "description": "Select or enter a version of Rust to install."
+ },
+ "profile": {
+ "type": "string",
+ "proposals": ["minimal", "default", "complete"],
+ "default": "minimal",
+ "description": "Select a rustup install profile."
+ }
+ },
+ "extensions": [
+ "vadimcn.vscode-lldb",
+ "mutantdino.resourcemonitor",
+ "matklad.rust-analyzer",
+ "tamasfe.even-better-toml",
+ "serayuzgur.crates"
+ ],
+ "containerEnv": {
+ "CARGO_HOME": "/usr/local/cargo",
+ "RUSTUP_HOME": "/usr/local/rustup",
+ "PATH": "${CARGO_HOME}/bin:${PATH}"
+ },
+ "capAdd": [ "SYS_PTRACE" ],
+ "securityOpt": ["seccomp=unconfined"],
+ "settings": {
+ "lldb.executable": "/usr/bin/lldb",
+ "files.watcherExclude": {
+ "**/target/**": true
+ },
+ "rust-analyzer.checkOnSave.command": "clippy"
+ },
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/rust/install.sh b/collection/rust/install.sh
new file mode 100644
index 0000000..1d90b14
--- /dev/null
+++ b/collection/rust/install.sh
@@ -0,0 +1,199 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/rust.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./rust-debian.sh [CARGO_HOME] [RUSTUP_HOME] [non-root user] [add CARGO/RUSTUP_HOME to rc files flag] [whether to update rust] [Rust version] [rustup install profile]
+
+export CARGO_HOME=${1:-"/usr/local/cargo"}
+export RUSTUP_HOME=${2:-"/usr/local/rustup"}
+USERNAME=${3:-"automatic"}
+UPDATE_RC=${4:-"true"}
+UPDATE_RUST=${5:-"false"}
+RUST_VERSION=${6:-"latest"}
+RUSTUP_PROFILE=${7:-"minimal"}
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Ensure that login shells get the correct path if the user updated the PATH using ENV.
+rm -f /etc/profile.d/00-restore-env.sh
+echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
+chmod +x /etc/profile.d/00-restore-env.sh
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+updaterc() {
+ if [ "${UPDATE_RC}" = "true" ]; then
+ echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..."
+ if [[ "$(cat /etc/bash.bashrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/bash.bashrc
+ fi
+ if [ -f "/etc/zsh/zshrc" ] && [[ "$(cat /etc/zsh/zshrc)" != *"$1"* ]]; then
+ echo -e "$1" >> /etc/zsh/zshrc
+ fi
+ fi
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+export DEBIAN_FRONTEND=noninteractive
+
+# Install curl, lldb, python3-minimal,libpython and rust dependencies if missing
+if ! dpkg -s curl ca-certificates gnupg2 lldb python3-minimal gcc libc6-dev > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends curl ca-certificates gcc libc6-dev
+ apt-get -y install lldb python3-minimal libpython3.?
+fi
+
+architecture="$(dpkg --print-architecture)"
+download_architecture="${architecture}"
+case ${download_architecture} in
+ amd64)
+ download_architecture="x86_64"
+ ;;
+ arm64)
+ download_architecture="aarch64"
+ ;;
+ *) echo "(!) Architecture ${architecture} not supported."
+ exit 1
+ ;;
+esac
+
+# Install Rust
+umask 0002
+if ! cat /etc/group | grep -e "^rustlang:" > /dev/null 2>&1; then
+ groupadd -r rustlang
+fi
+usermod -a -G rustlang "${USERNAME}"
+mkdir -p "${CARGO_HOME}" "${RUSTUP_HOME}"
+chown :rustlang "${RUSTUP_HOME}" "${CARGO_HOME}"
+chmod g+r+w+s "${RUSTUP_HOME}" "${CARGO_HOME}"
+
+if [ "${RUST_VERSION}" = "none" ] || type rustup > /dev/null 2>&1; then
+ echo "Rust already installed. Skipping..."
+else
+ if [ "${RUST_VERSION}" != "latest" ] && [ "${RUST_VERSION}" != "lts" ] && [ "${RUST_VERSION}" != "stable" ]; then
+ # Find version using soft match
+ if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends git
+ fi
+ find_version_from_git_tags RUST_VERSION "https://github.com/rust-lang/rust" "tags/"
+ default_toolchain_arg="--default-toolchain ${RUST_VERSION}"
+ fi
+ echo "Installing Rust..."
+ # Download and verify rustup sha
+ mkdir -p /tmp/rustup/target/${download_architecture}-unknown-linux-gnu/release/
+ curl -sSL --proto '=https' --tlsv1.2 "https://static.rust-lang.org/rustup/dist/${download_architecture}-unknown-linux-gnu/rustup-init" -o /tmp/rustup/target/${download_architecture}-unknown-linux-gnu/release/rustup-init
+ curl -sSL --proto '=https' --tlsv1.2 "https://static.rust-lang.org/rustup/dist/${download_architecture}-unknown-linux-gnu/rustup-init.sha256" -o /tmp/rustup/rustup-init.sha256
+ cd /tmp/rustup
+ sha256sum -c rustup-init.sha256
+ chmod +x target/${download_architecture}-unknown-linux-gnu/release/rustup-init
+ target/${download_architecture}-unknown-linux-gnu/release/rustup-init -y --no-modify-path --profile ${RUSTUP_PROFILE} ${default_toolchain_arg}
+ cd ~
+ rm -rf /tmp/rustup
+fi
+
+export PATH=${CARGO_HOME}/bin:${PATH}
+if [ "${UPDATE_RUST}" = "true" ]; then
+ echo "Updating Rust..."
+ rustup update 2>&1
+fi
+echo "Installing common Rust dependencies..."
+rustup component add rls rust-analysis rust-src rustfmt clippy 2>&1
+
+# Add CARGO_HOME, RUSTUP_HOME and bin directory into bashrc/zshrc files (unless disabled)
+updaterc "$(cat << EOF
+export RUSTUP_HOME="${RUSTUP_HOME}"
+export CARGO_HOME="${CARGO_HOME}"
+if [[ "\${PATH}" != *"\${CARGO_HOME}/bin"* ]]; then export PATH="\${CARGO_HOME}/bin:\${PATH}"; fi
+EOF
+)"
+
+# Make files writable for rustlang group
+chmod -R g+r+w "${RUSTUP_HOME}" "${CARGO_HOME}"
+
+echo "Done!"
+
diff --git a/collection/sshd/feature.json b/collection/sshd/feature.json
new file mode 100644
index 0000000..4de71d9
--- /dev/null
+++ b/collection/sshd/feature.json
@@ -0,0 +1,17 @@
+{
+ "id": "sshd",
+ "name": "SSH server",
+ "options": {
+ "version": {
+ "type": "string",
+ "enum": ["latest"],
+ "default": "latest",
+ "description": "Currently unused."
+ }
+ },
+ "entrypoint": "/usr/local/share/ssh-init.sh",
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+} \ No newline at end of file
diff --git a/collection/sshd/install.sh b/collection/sshd/install.sh
new file mode 100644
index 0000000..eb33a09
--- /dev/null
+++ b/collection/sshd/install.sh
@@ -0,0 +1,169 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/sshd.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./sshd-debian.sh [SSH Port (don't use 22)] [non-root user] [start sshd now flag] [new password for user] [fix environment flag]
+#
+# Note: You can change your user's password with "sudo passwd $(whoami)" (or just "passwd" if running as root).
+
+SSHD_PORT=${1:-"2222"}
+USERNAME=${2:-"automatic"}
+START_SSHD=${3:-"false"}
+NEW_PASSWORD=${4:-"skip"}
+FIX_ENVIRONMENT=${5:-"true"}
+
+set -e
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Determine the appropriate non-root user
+if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
+ USERNAME=""
+ POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
+ for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
+ if id -u ${CURRENT_USER} > /dev/null 2>&1; then
+ USERNAME=${CURRENT_USER}
+ break
+ fi
+ done
+ if [ "${USERNAME}" = "" ]; then
+ USERNAME=root
+ fi
+elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
+ USERNAME=root
+fi
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+# Install openssh-server openssh-client
+check_packages openssh-server openssh-client lsof
+
+# Generate password if new password set to the word "random"
+if [ "${NEW_PASSWORD}" = "random" ]; then
+ NEW_PASSWORD="$(openssl rand -hex 16)"
+ EMIT_PASSWORD="true"
+elif [ "${NEW_PASSWORD}" != "skip" ]; then
+ # If new password not set to skip, set it for the specified user
+ echo "${USERNAME}:${NEW_PASSWORD}" | chpasswd
+fi
+
+# Add user to ssh group
+if [ "${USERNAME}" != "root" ]; then
+ usermod -aG ssh ${USERNAME}
+fi
+
+# Setup sshd
+mkdir -p /var/run/sshd
+sed -i 's/session\s*required\s*pam_loginuid\.so/session optional pam_loginuid.so/g' /etc/pam.d/sshd
+sed -i 's/#*PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config
+sed -i -E "s/#*\s*Port\s+.+/Port ${SSHD_PORT}/g" /etc/ssh/sshd_config
+# Need to UsePAM so /etc/environment is processed
+sed -i -E "s/#?\s*UsePAM\s+.+/UsePAM yes/g" /etc/ssh/sshd_config
+
+# Script to store variables that exist at the time the ENTRYPOINT is fired
+store_env_script="$(cat << 'EOF'
+# Wire in codespaces secret processing to zsh if present (since may have been added to image after script was run)
+if [ -f /etc/zsh/zlogin ] && ! grep '/etc/profile.d/00-restore-secrets.sh' /etc/zsh/zlogin > /dev/null 2>&1; then
+ echo -e "if [ -f /etc/profile.d/00-restore-secrets.sh ]; then . /etc/profile.d/00-restore-secrets.sh; fi\n$(cat /etc/zsh/zlogin 2>/dev/null || echo '')" | sudoIf tee /etc/zsh/zlogin > /dev/null
+fi
+EOF
+)"
+
+# Script to ensure login shells get the latest Codespaces secrets
+restore_secrets_script="$(cat << 'EOF'
+#!/bin/sh
+if [ "${CODESPACES}" != "true" ] || [ "${VSCDC_FIXED_SECRETS}" = "true" ] || [ ! -z "${GITHUB_CODESPACES_TOKEN}" ]; then
+ # Not codespaces, already run, or secrets already in environment, so return
+ return
+fi
+if [ -f /workspaces/.codespaces/shared/.env-secrets ]; then
+ while read line
+ do
+ key=$(echo $line | sed "s/=.*//")
+ value=$(echo $line | sed "s/$key=//1")
+ decodedValue=$(echo $value | base64 -d)
+ export $key="$decodedValue"
+ done < /workspaces/.codespaces/shared/.env-secrets
+fi
+export VSCDC_FIXED_SECRETS=true
+EOF
+)"
+
+# Write out a scripts that can be referenced as an ENTRYPOINT to auto-start sshd and fix login environments
+tee /usr/local/share/ssh-init.sh > /dev/null \
+<< 'EOF'
+#!/usr/bin/env bash
+# This script is intended to be run as root with a container that runs as root (even if you connect with a different user)
+# However, it supports running as a user other than root if passwordless sudo is configured for that same user.
+
+set -e
+
+sudoIf()
+{
+ if [ "$(id -u)" -ne 0 ]; then
+ sudo "$@"
+ else
+ "$@"
+ fi
+}
+
+EOF
+if [ "${FIX_ENVIRONMENT}" = "true" ]; then
+ echo "${store_env_script}" >> /usr/local/share/ssh-init.sh
+ echo "${restore_secrets_script}" > /etc/profile.d/00-restore-secrets.sh
+ chmod +x /etc/profile.d/00-restore-secrets.sh
+ # Wire in zsh if present
+ if type zsh > /dev/null 2>&1; then
+ echo -e "if [ -f /etc/profile.d/00-restore-secrets.sh ]; then . /etc/profile.d/00-restore-secrets.sh; fi\n$(cat /etc/zsh/zlogin 2>/dev/null || echo '')" > /etc/zsh/zlogin
+ fi
+fi
+tee -a /usr/local/share/ssh-init.sh > /dev/null \
+<< 'EOF'
+
+# ** Start SSH server **
+sudoIf /etc/init.d/ssh start 2>&1 | sudoIf tee /tmp/sshd.log > /dev/null
+
+set +e
+exec "$@"
+EOF
+chmod +x /usr/local/share/ssh-init.sh
+
+# If we should start sshd now, do so
+if [ "${START_SSHD}" = "true" ]; then
+ /usr/local/share/ssh-init.sh
+fi
+
+# Output success details
+echo -e "Done!\n\n- Port: ${SSHD_PORT}\n- User: ${USERNAME}"
+if [ "${EMIT_PASSWORD}" = "true" ]; then
+ echo "- Password: ${NEW_PASSWORD}"
+fi
+echo -e "\nForward port ${SSHD_PORT} to your local machine and run:\n\n ssh -p ${SSHD_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null ${USERNAME}@localhost\n"
diff --git a/collection/terraform/feature.json b/collection/terraform/feature.json
new file mode 100644
index 0000000..02670a5
--- /dev/null
+++ b/collection/terraform/feature.json
@@ -0,0 +1,39 @@
+{
+ "id": "terraform",
+ "name": "Terraform, tflint, and TFGrunt",
+ "options": {
+ "version": {
+ "type": "string",
+ "proposals": ["latest", "1.1", "1.0", "0.15"],
+ "default": "latest",
+ "description": "Terraform version"
+ },
+ "tflint": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Tflint version"
+ },
+ "terragrunt": {
+ "type": "string",
+ "proposals": ["latest"],
+ "default": "latest",
+ "description": "Terragrunt version"
+ }
+ },
+ "extensions": [
+ "HashiCorp.terraform",
+ "ms-azuretools.vscode-azureterraform"
+ ],
+ "settings": {
+ "terraform.languageServer": {
+ "enabled": true,
+ "args": []
+ },
+ "azureTerraform.terminal": "integrated",
+ "install": {
+ "app": "",
+ "file": "install.sh"
+ }
+ }
+} \ No newline at end of file
diff --git a/collection/terraform/install.sh b/collection/terraform/install.sh
new file mode 100644
index 0000000..f2614ae
--- /dev/null
+++ b/collection/terraform/install.sh
@@ -0,0 +1,218 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
+#-------------------------------------------------------------------------------------------------------------
+#
+# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/terraform.md
+# Maintainer: The VS Code and Codespaces Teams
+#
+# Syntax: ./terraform-debian.sh [terraform version] [tflint version] [terragrunt version] [terraform SHA] [tflint SHA] [terragrunt SHA]
+
+set -e
+
+TERRAFORM_VERSION="${1:-"latest"}"
+TFLINT_VERSION="${2:-"latest"}"
+TERRAGRUNT_VERSION="${3:-"latest"}"
+TERRAFORM_SHA256="${4:-"automatic"}"
+TFLINT_SHA256="${5:-"automatic"}"
+TERRAGRUNT_SHA256="${6:-"automatic"}"
+
+TERRAFORM_GPG_KEY="72D7468F"
+TFLINT_GPG_KEY_URI="https://raw.githubusercontent.com/terraform-linters/tflint/master/8CE69160EB3F2FE9.key"
+GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
+keyserver hkps://keys.openpgp.org
+keyserver hkp://keyserver.pgp.com"
+
+architecture="$(uname -m)"
+case ${architecture} in
+ x86_64) architecture="amd64";;
+ aarch64 | armv8*) architecture="arm64";;
+ aarch32 | armv7* | armvhf*) architecture="arm";;
+ i?86) architecture="386";;
+ *) echo "(!) Architecture ${architecture} unsupported"; exit 1 ;;
+esac
+
+if [ "$(id -u)" -ne 0 ]; then
+ echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
+ exit 1
+fi
+
+# Get central common setting
+get_common_setting() {
+ if [ "${common_settings_file_loaded}" != "true" ]; then
+ curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
+ common_settings_file_loaded=true
+ fi
+ if [ -f "/tmp/vsdc-settings.env" ]; then
+ local multi_line=""
+ if [ "$2" = "true" ]; then multi_line="-z"; fi
+ local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
+ if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
+ fi
+ echo "$1=${!1}"
+}
+
+# Import the specified key in a variable name passed in as
+receive_gpg_keys() {
+ get_common_setting $1
+ local keys=${!1}
+ get_common_setting GPG_KEY_SERVERS true
+ local keyring_args=""
+ if [ ! -z "$2" ]; then
+ keyring_args="--no-default-keyring --keyring $2"
+ fi
+
+ # Use a temporary locaiton for gpg keys to avoid polluting image
+ export GNUPGHOME="/tmp/tmp-gnupg"
+ mkdir -p ${GNUPGHOME}
+ chmod 700 ${GNUPGHOME}
+ echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf
+ # GPG key download sometimes fails for some reason and retrying fixes it.
+ local retry_count=0
+ local gpg_ok="false"
+ set +e
+ until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
+ do
+ echo "(*) Downloading GPG key..."
+ ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true"
+ if [ "${gpg_ok}" != "true" ]; then
+ echo "(*) Failed getting key, retring in 10s..."
+ (( retry_count++ ))
+ sleep 10s
+ fi
+ done
+ set -e
+ if [ "${gpg_ok}" = "false" ]; then
+ echo "(!) Failed to get gpg key."
+ exit 1
+ fi
+}
+
+# Figure out correct version of a three part version number is not passed
+find_version_from_git_tags() {
+ local variable_name=$1
+ local requested_version=${!variable_name}
+ if [ "${requested_version}" = "none" ]; then return; fi
+ local repository=$2
+ local prefix=${3:-"tags/v"}
+ local separator=${4:-"."}
+ local last_part_optional=${5:-"false"}
+ if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then
+ local escaped_separator=${separator//./\\.}
+ local last_part
+ if [ "${last_part_optional}" = "true" ]; then
+ last_part="(${escaped_separator}[0-9]+)?"
+ else
+ last_part="${escaped_separator}[0-9]+"
+ fi
+ local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$"
+ local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)"
+ if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then
+ declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)"
+ else
+ set +e
+ declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")"
+ set -e
+ fi
+ fi
+ if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then
+ echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2
+ exit 1
+ fi
+ echo "${variable_name}=${!variable_name}"
+}
+
+# Function to run apt-get if needed
+apt_get_update_if_needed()
+{
+ if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
+ echo "Running apt-get update..."
+ apt-get update
+ else
+ echo "Skipping apt-get update."
+ fi
+}
+
+# Checks if packages are installed and installs them if not
+check_packages() {
+ if ! dpkg -s "$@" > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends "$@"
+ fi
+}
+
+# Ensure apt is in non-interactive to avoid prompts
+export DEBIAN_FRONTEND=noninteractive
+
+# Install dependencies if missing
+check_packages curl ca-certificates gnupg2 dirmngr coreutils unzip
+if ! type git > /dev/null 2>&1; then
+ apt_get_update_if_needed
+ apt-get -y install --no-install-recommends git
+fi
+
+# Verify requested version is available, convert latest
+find_version_from_git_tags TERRAFORM_VERSION 'https://github.com/hashicorp/terraform'
+find_version_from_git_tags TFLINT_VERSION 'https://github.com/terraform-linters/tflint'
+find_version_from_git_tags TERRAGRUNT_VERSION 'https://github.com/gruntwork-io/terragrunt'
+
+mkdir -p /tmp/tf-downloads
+cd /tmp/tf-downloads
+
+# Install Terraform, tflint, Terragrunt
+echo "Downloading terraform..."
+terraform_filename="terraform_${TERRAFORM_VERSION}_linux_${architecture}.zip"
+curl -sSL -o ${terraform_filename} "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/${terraform_filename}"
+if [ "${TERRAFORM_SHA256}" != "dev-mode" ]; then
+ if [ "${TERRAFORM_SHA256}" = "automatic" ]; then
+ receive_gpg_keys TERRAFORM_GPG_KEY
+ curl -sSL -o terraform_SHA256SUMS https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_SHA256SUMS
+ curl -sSL -o terraform_SHA256SUMS.sig https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_SHA256SUMS.${TERRAFORM_GPG_KEY}.sig
+ gpg --verify terraform_SHA256SUMS.sig terraform_SHA256SUMS
+ else
+ echo "${TERRAFORM_SHA256} *${terraform_filename}" > terraform_SHA256SUMS
+ fi
+ sha256sum --ignore-missing -c terraform_SHA256SUMS
+fi
+unzip ${terraform_filename}
+mv -f terraform /usr/local/bin/
+
+if [ "${TFLINT_VERSION}" != "none" ]; then
+ echo "Downloading tflint..."
+ TFLINT_FILENAME="tflint_linux_${architecture}.zip"
+ curl -sSL -o /tmp/tf-downloads/${TFLINT_FILENAME} https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/${TFLINT_FILENAME}
+ if [ "${TFLINT_SHA256}" != "dev-mode" ]; then
+ if [ "${TFLINT_SHA256}" = "automatic" ]; then
+ get_common_setting TFLINT_GPG_KEY_URI
+ curl -sSL -o tflint_key "${TFLINT_GPG_KEY_URI}"
+ gpg -q --import tflint_key
+ curl -sSL -o tflint_checksums.txt https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt
+ curl -sSL -o tflint_checksums.txt.sig https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/checksums.txt.sig
+ gpg --verify tflint_checksums.txt.sig tflint_checksums.txt
+ else
+ echo "${TFLINT_SHA256} *${TFLINT_FILENAME}" > tflint_checksums.txt
+ fi
+ sha256sum --ignore-missing -c tflint_checksums.txt
+ fi
+ unzip /tmp/tf-downloads/${TFLINT_FILENAME}
+ mv -f tflint /usr/local/bin/
+fi
+if [ "${TERRAGRUNT_VERSION}" != "none" ]; then
+ echo "Downloading Terragrunt..."
+ terragrunt_filename="terragrunt_linux_${architecture}"
+ curl -sSL -o /tmp/tf-downloads/${terragrunt_filename} https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VERSION}/${terragrunt_filename}
+ if [ "${TERRAGRUNT_SHA256}" != "dev-mode" ]; then
+ if [ "${TERRAGRUNT_SHA256}" = "automatic" ]; then
+ curl -sSL -o terragrunt_SHA256SUMS https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VERSION}/SHA256SUMS
+ else
+ echo "${TERRAGRUNT_SHA256} *${terragrunt_filename}" > terragrunt_SHA256SUMS
+ fi
+ sha256sum --ignore-missing -c terragrunt_SHA256SUMS
+ fi
+ chmod a+x /tmp/tf-downloads/${terragrunt_filename}
+ mv -f /tmp/tf-downloads/${terragrunt_filename} /usr/local/bin/terragrunt
+fi
+
+rm -rf /tmp/tf-downloads ${GNUPGHOME}
+echo "Done!"