Go теперь фича над базовым образом

This commit is contained in:
NeonXP 2024-01-28 20:43:11 +03:00
parent 6c00916212
commit 11412b2e72
61 changed files with 5528 additions and 13 deletions

View file

@ -3,21 +3,12 @@ FROM ${ARCH}ubuntu:latest
RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \
apt-get -y install software-properties-common ca-certificates curl && \
add-apt-repository ppa:longsleep/golang-backports && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_21.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
apt-get update && \
apt-get -y install --no-install-recommends gnupg2 procps sudo git libpq-dev make golang nodejs && \
apt-get -y install --no-install-recommends gnupg2 procps sudo git make nodejs && \
useradd -m -d /home/vscode -s /bin/bash vscode && adduser vscode sudo && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER vscode
RUN go install github.com/go-delve/delve/cmd/dlv@latest
RUN go install golang.org/x/tools/gopls@latest
RUN go install github.com/cweill/gotests/gotests@v1.6.0
RUN go install github.com/fatih/gomodifytags@v1.16.0
RUN go install github.com/josharian/impl@v1.1.0
RUN go install github.com/haya14busa/goplay/cmd/goplay@v1.0.0
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
SHELL ["/bin/bash", "-c", "-l"]

View file

@ -8,9 +8,6 @@
"customizations": {
"vscode": {
"extensions": [
"golang.go",
"neonxp.gotools",
"r3inbowari.gomodexplorer",
"ms-vscode.makefile-tools",
"redhat.vscode-yaml",
"humao.rest-client",

View file

@ -0,0 +1,26 @@
## OS Support
This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, RockyLinux, and Alpine Linux.
## Using with dev container images
This Feature is used in many of the [dev container images](https://github.com/search?q=repo%3Adevcontainers%2Fimages+%22ghcr.io%2Fdevcontainers%2Ffeatures%2Fcommon-utils%22&type=code), as a result
these images have already allocated UID & GID 1000. Attempting to add this Feature with UID 1000 and/or GID 1000 on top of such a dev container image will result in an error when building the dev container.
## Customizing the command prompt
By default, this script provides a custom command prompt that includes information about the git repository for the current folder. However, with certain large repositories, this can result in a slow command prompt due to the performance of needed git operations.
For performance reasons, a "dirty" indicator that tells you whether or not there are uncommitted changes is disabled by default. You can opt to turn this on for smaller repositories by entering the following in a terminal or adding it to your `postCreateCommand`:
```bash
git config devcontainers-theme.show-dirty 1
```
To completely disable the git portion of the prompt for the current folder's repository, you can use this configuration setting instead:
```bash
git config devcontainers-theme.hide-status 1
```
For `zsh`, the default theme is a [standard Oh My Zsh! theme](https://ohmyz.sh/). You may pick a different one by modifying the `ZSH_THEME` variable in `~/.zshrc`.

View file

@ -0,0 +1,58 @@
# Common Utilities (common-utils)
Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-root user.
## Example Usage
```json
"features": {
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz:2": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| installZsh | Install ZSH? | boolean | true |
| configureZshAsDefaultShell | Change default shell to ZSH? | boolean | false |
| installOhMyZsh | Install Oh My Zsh!? | boolean | true |
| installOhMyZshConfig | Allow installing the default dev container .zshrc templates? | boolean | true |
| upgradePackages | Upgrade OS packages? | boolean | true |
| username | Enter name of a non-root user to configure or none to skip | string | automatic |
| userUid | Enter UID for non-root user | string | automatic |
| userGid | Enter GID for non-root user | string | automatic |
| nonFreePackages | Add packages from non-free Debian repository? (Debian only) | boolean | false |
## OS Support
This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, RockyLinux, and Alpine Linux.
## Using with dev container images
This Feature is used in many of the [dev container images](https://github.com/search?q=repo%3Adevcontainers%2Fimages+%22ghcr.io%2Fdevcontainers%2Ffeatures%2Fcommon-utils%22&type=code), as a result
these images have already allocated UID & GID 1000. Attempting to add this Feature with UID 1000 and/or GID 1000 on top of such a dev container image will result in an error when building the dev container.
## Customizing the command prompt
By default, this script provides a custom command prompt that includes information about the git repository for the current folder. However, with certain large repositories, this can result in a slow command prompt due to the performance of needed git operations.
For performance reasons, a "dirty" indicator that tells you whether or not there are uncommitted changes is disabled by default. You can opt to turn this on for smaller repositories by entering the following in a terminal or adding it to your `postCreateCommand`:
```bash
git config devcontainers-theme.show-dirty 1
```
To completely disable the git portion of the prompt for the current folder's repository, you can use this configuration setting instead:
```bash
git config devcontainers-theme.hide-status 1
```
For `zsh`, the default theme is a [standard Oh My Zsh! theme](https://ohmyz.sh/). You may pick a different one by modifying the `ZSH_THEME` variable in `~/.zshrc`.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/common-utils/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,16 @@
#!/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

View file

@ -0,0 +1,35 @@
#!/bin/sh
# Load meta.env
if [ -f "/usr/local/etc/vscode-dev-containers/meta.env" ]; then
. /usr/local/etc/vscode-dev-containers/meta.env
fi
if [ -f "/usr/local/etc/dev-containers/meta.env" ]; then
. /usr/local/etc/dev-containers/meta.env
fi
# 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 "${GIT_REPOSITORY_REVISION}" ]; then echo "- Source code revision: ${GIT_REPOSITORY_REVISION}"; fi
if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi
if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi
echo

View file

@ -0,0 +1,7 @@
#!/bin/sh
set -e
if [ -d "/run/systemd/system" ]; then
exec /bin/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

View file

@ -0,0 +1,69 @@
{
"id": "common-utils",
"version": "2.3.1",
"name": "Common Utilities",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/common-utils",
"description": "Installs a set of common command line utilities, Oh My Zsh!, and sets up a non-root user.",
"options": {
"installZsh": {
"type": "boolean",
"default": true,
"description": "Install ZSH?"
},
"configureZshAsDefaultShell": {
"type": "boolean",
"default": false,
"description": "Change default shell to ZSH?"
},
"installOhMyZsh": {
"type": "boolean",
"default": true,
"description": "Install Oh My Zsh!?"
},
"installOhMyZshConfig": {
"type": "boolean",
"default": true,
"description": "Allow installing the default dev container .zshrc templates?"
},
"upgradePackages": {
"type": "boolean",
"default": true,
"description": "Upgrade OS packages?"
},
"username": {
"type": "string",
"proposals": [
"devcontainer",
"vscode",
"codespace",
"none",
"automatic"
],
"default": "automatic",
"description": "Enter name of a non-root user to configure or none to skip"
},
"userUid": {
"type": "string",
"proposals": [
"1001",
"automatic"
],
"default": "automatic",
"description": "Enter UID for non-root user"
},
"userGid": {
"type": "string",
"proposals": [
"1001",
"automatic"
],
"default": "automatic",
"description": "Enter GID for non-root user"
},
"nonFreePackages": {
"type": "boolean",
"default": false,
"description": "Add packages from non-free Debian repository? (Debian only)"
}
}
}

View file

@ -0,0 +1,36 @@
#!/bin/sh
#-------------------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://github.com/devcontainers/features/blob/main/LICENSE for license information.
#-------------------------------------------------------------------------------------------------------------------------
#
# Docs: https://github.com/devcontainers/features/tree/main/src/common-utils
# Maintainer: The Dev Container spec maintainers
set -e
INSTALL_ZSH="${INSTALLZSH:-"true"}"
CONFIGURE_ZSH_AS_DEFAULT_SHELL="${CONFIGUREZSHASDEFAULTSHELL:-"false"}"
INSTALL_OH_MY_ZSH="${INSTALLOHMYZSH:-"true"}"
INSTALL_OH_MY_ZSH_CONFIG="${INSTALLOHMYZSHCONFIG:-"true"}"
UPGRADE_PACKAGES="${UPGRADEPACKAGES:-"true"}"
USERNAME="${USERNAME:-"automatic"}"
USER_UID="${UID:-"automatic"}"
USER_GID="${GID:-"automatic"}"
ADD_NON_FREE_PACKAGES="${NONFREEPACKAGES:-"false"}"
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
# If we're using Alpine, install bash before executing
. /etc/os-release
if [ "${ID}" = "alpine" ]; then
apk add --no-cache bash
fi
exec /bin/bash "$(dirname $0)/main.sh" "$@"
exit $?

View file

@ -0,0 +1,573 @@
#!/bin/bash
#-------------------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://github.com/devcontainers/features/blob/main/LICENSE for license information.
#-------------------------------------------------------------------------------------------------------------------------
#
# Docs: https://github.com/devcontainers/features/tree/main/src/common-utils
# Maintainer: The Dev Container spec maintainers
set -e
INSTALL_ZSH="${INSTALLZSH:-"true"}"
CONFIGURE_ZSH_AS_DEFAULT_SHELL="${CONFIGUREZSHASDEFAULTSHELL:-"false"}"
INSTALL_OH_MY_ZSH="${INSTALLOHMYZSH:-"true"}"
INSTALL_OH_MY_ZSH_CONFIG="${INSTALLOHMYZSHCONFIG:-"true"}"
UPGRADE_PACKAGES="${UPGRADEPACKAGES:-"true"}"
USERNAME="${USERNAME:-"automatic"}"
USER_UID="${USERUID:-"automatic"}"
USER_GID="${USERGID:-"automatic"}"
ADD_NON_FREE_PACKAGES="${NONFREEPACKAGES:-"false"}"
MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
FEATURE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Debian / Ubuntu packages
install_debian_packages() {
# Ensure apt is in non-interactive to avoid prompts
export DEBIAN_FRONTEND=noninteractive
local package_list=""
if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
package_list="${package_list} \
apt-utils \
openssh-client \
gnupg2 \
dirmngr \
iproute2 \
procps \
lsof \
htop \
net-tools \
psmisc \
curl \
tree \
wget \
rsync \
ca-certificates \
unzip \
bzip2 \
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-ust[0-9] \
libstdc++6 \
zlib1g \
locales \
sudo \
ncdu \
man-db \
strace \
manpages \
manpages-dev \
init-system-helpers"
# Include libssl1.1 if available
if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then
package_list="${package_list} libssl1.1"
fi
# Include libssl3 if available
if [[ ! -z $(apt-cache --names-only search ^libssl3$) ]]; then
package_list="${package_list} libssl3"
fi
# Include appropriate version of libssl1.0.x if available
local libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
if [ "$(echo "$libssl_package" | 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
package_list="${package_list} libssl1.0.0"
fi
fi
# Include git if not already installed (may be more recent than distro version)
if ! type git > /dev/null 2>&1; then
package_list="${package_list} git"
fi
fi
# 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
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..."
package_list="${package_list} manpages-posix manpages-posix-dev"
fi
# Install the list of packages
echo "Packages to verify are installed: ${package_list}"
rm -rf /var/lib/apt/lists/*
apt-get update -y
apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
# Install zsh (and recommended packages) if needed
if [ "${INSTALL_ZSH}" = "true" ] && ! type zsh > /dev/null 2>&1; then
apt-get install -y zsh
fi
# Get to latest versions of all packages
if [ "${UPGRADE_PACKAGES}" = "true" ]; then
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
PACKAGES_ALREADY_INSTALLED="true"
# Clean up
apt-get -y clean
rm -rf /var/lib/apt/lists/*
}
# RedHat / RockyLinux / CentOS / Fedora packages
install_redhat_packages() {
local package_list=""
local remove_epel="false"
local install_cmd=dnf
if ! type dnf > /dev/null 2>&1; then
install_cmd=yum
fi
if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
package_list="${package_list} \
gawk \
openssh-clients \
gnupg2 \
iproute \
procps \
lsof \
net-tools \
psmisc \
wget \
ca-certificates \
rsync \
unzip \
zip \
nano \
vim-minimal \
less \
jq \
openssl-libs \
krb5-libs \
libicu \
zlib \
sudo \
sed \
grep \
which \
man-db \
strace"
# rockylinux:9 installs 'curl-minimal' which clashes with 'curl'
# Install 'curl' for every OS except this rockylinux:9
if [[ "${ID}" = "rocky" ]] && [[ "${VERSION}" != *"9."* ]]; then
package_list="${package_list} curl"
fi
# Install OpenSSL 1.0 compat if needed
if ${install_cmd} -q list compat-openssl10 >/dev/null 2>&1; then
package_list="${package_list} compat-openssl10"
fi
# Install lsb_release if available
if ${install_cmd} -q list redhat-lsb-core >/dev/null 2>&1; then
package_list="${package_list} redhat-lsb-core"
fi
# Install git if not already installed (may be more recent than distro version)
if ! type git > /dev/null 2>&1; then
package_list="${package_list} git"
fi
# Install EPEL repository if needed (required to install 'jq' for CentOS)
if ! ${install_cmd} -q list jq >/dev/null 2>&1; then
${install_cmd} -y install epel-release
remove_epel="true"
fi
fi
# Install zsh if needed
if [ "${INSTALL_ZSH}" = "true" ] && ! type zsh > /dev/null 2>&1; then
package_list="${package_list} zsh"
fi
if [ -n "${package_list}" ]; then
${install_cmd} -y install ${package_list}
fi
# Get to latest versions of all packages
if [ "${UPGRADE_PACKAGES}" = "true" ]; then
${install_cmd} upgrade -y
fi
if [[ "${remove_epel}" = "true" ]]; then
${install_cmd} -y remove epel-release
fi
PACKAGES_ALREADY_INSTALLED="true"
}
# Alpine Linux packages
install_alpine_packages() {
apk update
if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
apk add --no-cache \
openssh-client \
gnupg \
procps \
lsof \
htop \
net-tools \
psmisc \
curl \
wget \
rsync \
ca-certificates \
unzip \
zip \
nano \
vim \
less \
jq \
libgcc \
libstdc++ \
krb5-libs \
libintl \
libssl1.1 \
lttng-ust \
tzdata \
userspace-rcu \
zlib \
sudo \
coreutils \
sed \
grep \
which \
ncdu \
shadow \
strace
# Install man pages - package name varies between 3.12 and earlier versions
if apk info man > /dev/null 2>&1; then
apk add --no-cache man man-pages
else
apk add --no-cache mandoc man-pages
fi
# Install git if not already installed (may be more recent than distro version)
if ! type git > /dev/null 2>&1; then
apk add --no-cache git
fi
fi
# Install zsh if needed
if [ "${INSTALL_ZSH}" = "true" ] && ! type zsh > /dev/null 2>&1; then
apk add --no-cache zsh
fi
PACKAGES_ALREADY_INSTALLED="true"
}
# ******************
# ** Main section **
# ******************
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
# 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 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
# Bring in ID, ID_LIKE, VERSION_ID, VERSION_CODENAME
. /etc/os-release
# Get an adjusted ID independent of distro variants
if [ "${ID}" = "debian" ] || [ "${ID_LIKE}" = "debian" ]; then
ADJUSTED_ID="debian"
elif [[ "${ID}" = "rhel" || "${ID}" = "fedora" || "${ID}" = "mariner" || "${ID_LIKE}" = *"rhel"* || "${ID_LIKE}" = *"fedora"* || "${ID_LIKE}" = *"mariner"* ]]; then
ADJUSTED_ID="rhel"
elif [ "${ID}" = "alpine" ]; then
ADJUSTED_ID="alpine"
else
echo "Linux distro ${ID} not supported."
exit 1
fi
# Install packages for appropriate OS
case "${ADJUSTED_ID}" in
"debian")
install_debian_packages
;;
"rhel")
install_redhat_packages
;;
"alpine")
install_alpine_packages
;;
esac
# If in automatic mode, determine if a user already exists, if not use vscode
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
if [ "${_REMOTE_USER}" != "root" ]; then
USERNAME="${_REMOTE_USER}"
else
USERNAME=""
POSSIBLE_USERS=("devcontainer" "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
fi
elif [ "${USERNAME}" = "none" ]; then
USERNAME=root
USER_UID=0
USER_GID=0
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_home="/root"
# Check if user already has a home directory other than /home/${USERNAME}
elif [ "/home/${USERNAME}" != $( getent passwd $USERNAME | cut -d: -f6 ) ]; then
user_home=$( getent passwd $USERNAME | cut -d: -f6 )
else
user_home="/home/${USERNAME}"
if [ ! -d "${user_home}" ]; then
mkdir -p "${user_home}"
chown ${USERNAME}:${group_name} "${user_home}"
fi
fi
# Restore user .bashrc / .profile / .zshrc defaults from skeleton file if it doesn't exist or is empty
possible_rc_files=( ".bashrc" ".profile" )
[ "$INSTALL_OH_MY_ZSH_CONFIG" == "true" ] && possible_rc_files+=('.zshrc')
[ "$INSTALL_ZSH" == "true" ] && possible_rc_files+=('.zprofile')
for rc_file in "${possible_rc_files[@]}"; do
if [ -f "/etc/skel/${rc_file}" ]; then
if [ ! -e "${user_home}/${rc_file}" ] || [ ! -s "${user_home}/${rc_file}" ]; then
cp "/etc/skel/${rc_file}" "${user_home}/${rc_file}"
chown ${USERNAME}:${group_name} "${user_home}/${rc_file}"
fi
fi
done
# Add RC snippet and custom bash prompt
if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then
case "${ADJUSTED_ID}" in
"debian")
global_rc_path="/etc/bash.bashrc"
;;
"rhel")
global_rc_path="/etc/bashrc"
;;
"alpine")
global_rc_path="/etc/bash/bashrc"
# /etc/bash/bashrc does not exist in alpine 3.14 & 3.15
mkdir -p /etc/bash
;;
esac
cat "${FEATURE_DIR}/scripts/rc_snippet.sh" >> ${global_rc_path}
cat "${FEATURE_DIR}/scripts/bash_theme_snippet.sh" >> "${user_home}/.bashrc"
if [ "${USERNAME}" != "root" ]; then
cat "${FEATURE_DIR}/scripts/bash_theme_snippet.sh" >> "/root/.bashrc"
chown ${USERNAME}:${group_name} "${user_home}/.bashrc"
fi
RC_SNIPPET_ALREADY_ADDED="true"
fi
# Optionally configure zsh and Oh My Zsh!
if [ "${INSTALL_ZSH}" = "true" ]; then
if [ ! -f "${user_home}/.zprofile" ]; then
touch "${user_home}/.zprofile"
echo 'source $HOME/.profile' >> "${user_home}/.zprofile" # TODO: Reconsider adding '.profile' to '.zprofile'
chown ${USERNAME}:${group_name} "${user_home}/.zprofile"
fi
if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
if [ "${ADJUSTED_ID}" = "rhel" ]; then
global_rc_path="/etc/zshrc"
else
global_rc_path="/etc/zsh/zshrc"
fi
cat "${FEATURE_DIR}/scripts/rc_snippet.sh" >> ${global_rc_path}
ZSH_ALREADY_INSTALLED="true"
fi
if [ "${CONFIGURE_ZSH_AS_DEFAULT_SHELL}" == "true" ]; then
# Fixing chsh always asking for a password on alpine linux
# ref: https://askubuntu.com/questions/812420/chsh-always-asking-a-password-and-get-pam-authentication-failure.
if [ ! -f "/etc/pam.d/chsh" ] || ! grep -Eq '^auth(.*)pam_rootok\.so$' /etc/pam.d/chsh; then
echo "auth sufficient pam_rootok.so" >> /etc/pam.d/chsh
elif [[ -n "$(awk '/^auth(.*)pam_rootok\.so$/ && !/^auth[[:blank:]]+sufficient[[:blank:]]+pam_rootok\.so$/' /etc/pam.d/chsh)" ]]; then
awk '/^auth(.*)pam_rootok\.so$/ { $2 = "sufficient" } { print }' /etc/pam.d/chsh > /tmp/chsh.tmp && mv /tmp/chsh.tmp /etc/pam.d/chsh
fi
chsh --shell /bin/zsh ${USERNAME}
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.
if [ "${INSTALL_OH_MY_ZSH}" = "true" ]; then
user_rc_file="${user_home}/.zshrc"
oh_my_install_dir="${user_home}/.oh-my-zsh"
template_path="${oh_my_install_dir}/templates/zshrc.zsh-template"
if [ ! -d "${oh_my_install_dir}" ]; then
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
# Shrink git while still enabling updates
cd "${oh_my_install_dir}"
git repack -a -d -f --depth=1 --window=1
fi
# Add Dev Containers theme
mkdir -p ${oh_my_install_dir}/custom/themes
cp -f "${FEATURE_DIR}/scripts/devcontainers.zsh-theme" "${oh_my_install_dir}/custom/themes/devcontainers.zsh-theme"
ln -sf "${oh_my_install_dir}/custom/themes/devcontainers.zsh-theme" "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme"
# Add devcontainer .zshrc template
if [ "$INSTALL_OH_MY_ZSH_CONFIG" = "true" ]; then
echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file}
sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="devcontainers"/g' ${user_rc_file}
fi
# Copy to non-root user if one is specified
if [ "${USERNAME}" != "root" ]; then
copy_to_user_files=("${oh_my_install_dir}")
[ -f "$user_rc_file" ] && copy_to_user_files+=("$user_rc_file")
cp -rf "${copy_to_user_files[@]}" /root
chown -R ${USERNAME}:${group_name} "${copy_to_user_files[@]}"
fi
fi
fi
# *********************************
# ** Ensure config directory **
# *********************************
user_config_dir="${user_home}/.config"
if [ ! -d "${user_config_dir}" ]; then
mkdir -p "${user_config_dir}"
chown ${USERNAME}:${group_name} "${user_config_dir}"
fi
# ****************************
# ** Utilities and commands **
# ****************************
# code shim, it fallbacks to code-insiders if code is not available
cp -f "${FEATURE_DIR}/bin/code" /usr/local/bin/
chmod +rx /usr/local/bin/code
# systemctl shim for Debian/Ubuntu - tells people to use 'service' if systemd is not running
if [ "${ADJUSTED_ID}" = "debian" ]; then
cp -f "${FEATURE_DIR}/bin/systemctl" /usr/local/bin/systemctl
chmod +rx /usr/local/bin/systemctl
fi
# Persist image metadata info, script if meta.env found in same directory
if [ -f "/usr/local/etc/vscode-dev-containers/meta.env" ] || [ -f "/usr/local/etc/dev-containers/meta.env" ]; then
cp -f "${FEATURE_DIR}/bin/devcontainer-info" /usr/local/bin/devcontainer-info
chmod +rx /usr/local/bin/devcontainer-info
fi
# Write marker file
if [ ! -d "/usr/local/etc/vscode-dev-containers" ]; then
mkdir -p "$(dirname "${MARKER_FILE}")"
fi
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!"

View file

@ -0,0 +1,25 @@
# bash theme - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-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 devcontainers-theme.hide-status 2>/dev/null)" != 1 ] && [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \
export BRANCH=$(git --no-optional-locks symbolic-ref --short HEAD 2>/dev/null || git --no-optional-locks rev-parse --short HEAD 2>/dev/null); \
if [ "${BRANCH}" != "" ]; then \
echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \
&& if [ "$(git config --get devcontainers-theme.show-dirty 2>/dev/null)" = 1 ] && \
git --no-optional-locks 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
export PROMPT_DIRTRIM=4

View file

@ -0,0 +1,26 @@
# Oh My Zsh! theme - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-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+='`\
if [ "$(git config --get devcontainers-theme.hide-status 2>/dev/null)" != 1 ] && [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \
export BRANCH=$(git --no-optional-locks symbolic-ref --short HEAD 2>/dev/null || git --no-optional-locks rev-parse --short HEAD 2>/dev/null); \
if [ "${BRANCH}" != "" ]; then \
echo -n "%{$fg_bold[cyan]%}(%{$fg_bold[red]%}${BRANCH}" \
&& if [ "$(git config --get devcontainers-theme.show-dirty 2>/dev/null)" = 1 ] && \
git --no-optional-locks ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \
echo -n " %{$fg_bold[yellow]%}✗"; \
fi \
&& echo -n "%{$fg_bold[cyan]%})%{$reset_color%} "; \
fi; \
fi`'
PROMPT+='%{$fg[white]%}$ %{$reset_color%}'
unset -f __zsh_prompt
}
__zsh_prompt

View file

@ -0,0 +1,26 @@
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

View file

@ -0,0 +1,16 @@
## Limitations
This docker-in-docker Dev Container Feature is roughly based on the [official docker-in-docker wrapper script](https://github.com/moby/moby/blob/master/hack/dind) that is part of the [Moby project](https://mobyproject.org/). With this in mind:
* As the name implies, the Feature is expected to work when the host is running Docker (or the OSS Moby container engine it is built on). It may be possible to get running in other container engines, but it has not been tested with them.
* The host and the container must be running on the same chip architecture. You will not be able to use it with an emulated x86 image with Docker Desktop on an Apple Silicon Mac, like in this example:
```
FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/typescript-node:16
```
See [Issue #219](https://github.com/devcontainers/features/issues/219) for more details.
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,51 @@
# Docker (Docker-in-Docker) (docker-in-docker)
Create child containers *inside* a container, independent from the host's docker instance. Installs Docker extension in the container along with needed CLIs.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a Docker/Moby Engine version. (Availability can vary by OS version.) | string | latest |
| moby | Install OSS Moby build instead of Docker CE | boolean | true |
| dockerDashComposeVersion | Default version of Docker Compose (v1 or v2 or none) | string | v1 |
| azureDnsAutoDetection | Allow automatically setting the dockerd DNS server when the installation script detects it is running in Azure | boolean | true |
| dockerDefaultAddressPool | Define default address pools for Docker networks. e.g. base=192.168.0.0/16,size=24 | string | - |
| installDockerBuildx | Install Docker Buildx | boolean | true |
## Customizations
### VS Code Extensions
- `ms-azuretools.vscode-docker`
## Limitations
This docker-in-docker Dev Container Feature is roughly based on the [official docker-in-docker wrapper script](https://github.com/moby/moby/blob/master/hack/dind) that is part of the [Moby project](https://mobyproject.org/). With this in mind:
* As the name implies, the Feature is expected to work when the host is running Docker (or the OSS Moby container engine it is built on). It may be possible to get running in other container engines, but it has not been tested with them.
* The host and the container must be running on the same chip architecture. You will not be able to use it with an emulated x86 image with Docker Desktop on an Apple Silicon Mac, like in this example:
```
FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/typescript-node:16
```
See [Issue #219](https://github.com/devcontainers/features/issues/219) for more details.
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/docker-in-docker/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,72 @@
{
"id": "docker-in-docker",
"version": "2.7.1",
"name": "Docker (Docker-in-Docker)",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-in-docker",
"description": "Create child containers *inside* a container, independent from the host's docker instance. Installs Docker extension in the container along with needed CLIs.",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"none",
"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": [
"none",
"v1",
"v2"
],
"default": "v1",
"description": "Default version of Docker Compose (v1 or v2 or none)"
},
"azureDnsAutoDetection": {
"type": "boolean",
"default": true,
"description": "Allow automatically setting the dockerd DNS server when the installation script detects it is running in Azure"
},
"dockerDefaultAddressPool": {
"type": "string",
"default": "",
"proposals": [],
"description": "Define default address pools for Docker networks. e.g. base=192.168.0.0/16,size=24"
},
"installDockerBuildx": {
"type": "boolean",
"default": true,
"description": "Install Docker Buildx"
}
},
"entrypoint": "/usr/local/share/docker-init.sh",
"privileged": true,
"containerEnv": {
"DOCKER_BUILDKIT": "1"
},
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-docker"
]
}
},
"mounts": [
{
"source": "dind-var-lib-docker-${devcontainerId}",
"target": "/var/lib/docker",
"type": "volume"
}
],
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

View file

@ -0,0 +1,482 @@
#!/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 Dev Container spec maintainers
DOCKER_VERSION="${VERSION:-"latest"}" # The Docker/Moby Engine + CLI should match in version
USE_MOBY="${MOBY:-"true"}"
DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v1"}" # v1 or v2 or none
AZURE_DNS_AUTO_DETECTION="${AZUREDNSAUTODETECTION:-"true"}"
DOCKER_DEFAULT_ADDRESS_POOL="${DOCKERDEFAULTADDRESSPOOL}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
INSTALL_DOCKER_BUILDX="${INSTALLDOCKERBUILDX:-"true"}"
MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="bookworm buster bullseye bionic focal jammy"
DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="bookworm buster bullseye bionic focal hirsute impish jammy"
# Default: Exit on any failure.
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
# 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
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
# 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 supported
if [ "${USE_MOBY}" = "true" ]; then
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
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 wget
if ! type git > /dev/null 2>&1; then
check_packages 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
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}
# Install compose
apt-get -y install --no-install-recommends docker-compose-plugin || echo "(*) Package docker-compose-plugin (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping."
fi
fi
echo "Finished installing docker / moby!"
# If 'docker-compose' command is to be included
if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then
# 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
check_packages python3-minimal python3-pip libffi-dev python3-venv
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
set +e
${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose
exit_code=$?
set -e
if [ ${exit_code} -ne 0 ]; then
# Temporary: https://github.com/devcontainers/features/issues/616
# See https://github.com/yaml/pyyaml/issues/601
echo "(*) Failed to install docker-compose via pipx. Trying via pip3..."
export PYTHONUSERBASE=/usr/local
pip3 install --disable-pip-version-check --no-cache-dir --user "Cython<3.0" pyyaml wheel docker-compose --no-build-isolation
fi
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
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."
# Clean up
rm -rf /var/lib/apt/lists/*
exit 0
fi
echo "docker-init doesn't exist, adding..."
if ! cat /etc/group | grep -e "^docker:" > /dev/null 2>&1; then
groupadd -r docker
fi
usermod -aG docker ${USERNAME}
if [ "${INSTALL_DOCKER_BUILDX}" = "true" ]; then
buildx_version="latest"
find_version_from_git_tags buildx_version "https://github.com/docker/buildx" "refs/tags/v"
echo "(*) Installing buildx ${buildx_version}..."
buildx_file_name="buildx-v${buildx_version}.linux-${architecture}"
cd /tmp && wget "https://github.com/docker/buildx/releases/download/v${buildx_version}/${buildx_file_name}"
mkdir -p ${_REMOTE_USER_HOME}/.docker/cli-plugins
mv ${buildx_file_name} ${_REMOTE_USER_HOME}/.docker/cli-plugins/docker-buildx
chmod +x ${_REMOTE_USER_HOME}/.docker/cli-plugins/docker-buildx
chown -R "${USERNAME}:docker" "${_REMOTE_USER_HOME}/.docker"
chmod -R g+r+w "${_REMOTE_USER_HOME}/.docker"
find "${_REMOTE_USER_HOME}/.docker" -type d -print0 | xargs -n 1 -0 chmod g+s
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
AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION}
DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL}
EOF
tee -a /usr/local/share/docker-init.sh > /dev/null \
<< 'EOF'
dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL} $(cat << 'INNEREOF'
# explicitly remove dockerd and containerd PID file to ensure that it can start properly if it was stopped uncleanly
find /run /var/run -iname 'docker*.pid' -delete || :
find /run /var/run -iname 'container*.pid' -delete || :
# -- Start: dind wrapper script --
# 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
set_cgroup_nesting()
{
# 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
}
# Set cgroup nesting, retrying if necessary
retry_cgroup_nesting=0
until [ "${retry_cgroup_nesting}" -eq "5" ];
do
set +e
set_cgroup_nesting
if [ $? -ne 0 ]; then
echo "(*) cgroup v2: Failed to enable nesting, retrying..."
else
break
fi
retry_cgroup_nesting=`expr $retry_cgroup_nesting + 1`
set -e
done
# -- End: dind wrapper script --
# Handle DNS
set +e
cat /etc/resolv.conf | grep -i 'internal.cloudapp.net' > /dev/null 2>&1
if [ $? -eq 0 ] && [ "${AZURE_DNS_AUTO_DETECTION}" = "true" ]
then
echo "Setting dockerd Azure DNS."
CUSTOMDNS="--dns 168.63.129.16"
else
echo "Not setting dockerd DNS manually."
CUSTOMDNS=""
fi
set -e
if [ -z "$DOCKER_DEFAULT_ADDRESS_POOL" ]
then
DEFAULT_ADDRESS_POOL=""
else
DEFAULT_ADDRESS_POOL="--default-address-pool $DOCKER_DEFAULT_ADDRESS_POOL"
fi
# Start docker/moby engine
( dockerd $CUSTOMDNS $DEFAULT_ADDRESS_POOL > /tmp/dockerd.log 2>&1 ) &
INNEREOF
)"
sudo_if() {
COMMAND="$*"
if [ "$(id -u)" -ne 0 ]; then
sudo $COMMAND
else
$COMMAND
fi
}
retry_docker_start_count=0
docker_ok="false"
until [ "${docker_ok}" = "true" ] || [ "${retry_docker_start_count}" -eq "5" ];
do
# 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
retry_count=0
until [ "${docker_ok}" = "true" ] || [ "${retry_count}" -eq "5" ];
do
sleep 1s
set +e
docker info > /dev/null 2>&1 && docker_ok="true"
set -e
retry_count=`expr $retry_count + 1`
done
if [ "${docker_ok}" != "true" ] && [ "${retry_docker_start_count}" != "4" ]; then
echo "(*) Failed to start docker, retrying..."
set +e
sudo_if pkill dockerd
sudo_if pkill containerd
set -e
fi
retry_docker_start_count=`expr $retry_docker_start_count + 1`
done
# 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
# Clean up
rm -rf /var/lib/apt/lists/*
echo 'docker-in-docker-debian script has completed!'

View file

@ -0,0 +1,61 @@
## Limitations
- As the name implies, the Feature is expected to work when the host is running Docker (or the OSS Moby container engine it is built on). It may be possible to get running in other container engines, but it has not been tested with them.
- The host and the container must be running on the same chip architecture. You will not be able to use it with an emulated x86 image with Docker Desktop on an Apple Silicon Mac, for example.
- This approach does not currently enable bind mounting the workspace folder by default, and cannot support folders outside of the workspace folder. Consider whether the [Docker-in-Docker Feature](../docker-in-docker) would better meet your needs given it does not have this limitation.
## Supporting bind mounts from the workspace folder
A common question that comes up is how you can use `bind` mounts from the Docker CLI from within the a dev container using this Feature (e.g. via `-v`). If you cannot use the [Docker-in-Docker Feature](../docker-in-docker), the only way to work around this is to use the **host**'s folder paths instead of the container's paths. There are 2 ways to do this
### 1. Use the `${localWorkspaceFolder}` as environment variable in your code
1. Add the following to `devcontainer.json`:
```json
"remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }
```
2. Usage with Docker commands
```bash
docker run -it --rm -v ${LOCAL_WORKSPACE_FOLDER}:/workspace debian bash
```
3. Usage with Docker-compose
```yaml
version: "3.9"
services:
debian:
image: debian
volumes:
- ${LOCAL_WORKSPACE_FOLDER:-./}:/workspace
```
- The defaults value `./` is added so that the `docker-compose.yaml` file can work when it is run outside of the container
### Change the workspace to `${localWorkspaceFolder}`
- This is useful if we don't want to edit the `docker-compose.yaml` file
1. Add the following to `devcontainer.json`
```json
"workspaceFolder": "${localWorkspaceFolder}",
"workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind"
```
2. Rebuild the container.
3. When the container first started with this settings, select the Workspace with the absolute path to the working directory inside the container
4. Docker commands with bind mount should work as they did outside of the devcontainer
> **Note:** There is no `${localWorkspaceFolder}` when using the **Clone Repository in Container Volume** command in the VS Code Dev Containers extension ([info](https://github.com/microsoft/vscode-remote-release/issues/6160#issuecomment-1014701007)).
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,96 @@
### **IMPORTANT NOTE**
- **Ids used to publish this Feature in the past - 'docker-from-docker'**
# Docker (docker-outside-of-docker) (docker-outside-of-docker)
Re-use the host docker socket, adding the Docker CLI to a container. Feature invokes a script to enable using a forwarded Docker socket within a container to run Docker commands.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a Docker/Moby CLI version. (Availability can vary by OS version.) | string | latest |
| moby | Install OSS Moby build instead of Docker CE | boolean | true |
| dockerDashComposeVersion | Compose version to use for docker-compose (v1 or v2 or none) | string | v2 |
| installDockerBuildx | Install Docker Buildx | boolean | true |
## Customizations
### VS Code Extensions
- `ms-azuretools.vscode-docker`
## Limitations
- As the name implies, the Feature is expected to work when the host is running Docker (or the OSS Moby container engine it is built on). It may be possible to get running in other container engines, but it has not been tested with them.
- The host and the container must be running on the same chip architecture. You will not be able to use it with an emulated x86 image with Docker Desktop on an Apple Silicon Mac, for example.
- This approach does not currently enable bind mounting the workspace folder by default, and cannot support folders outside of the workspace folder. Consider whether the [Docker-in-Docker Feature](../docker-in-docker) would better meet your needs given it does not have this limitation.
## Supporting bind mounts from the workspace folder
A common question that comes up is how you can use `bind` mounts from the Docker CLI from within the a dev container using this Feature (e.g. via `-v`). If you cannot use the [Docker-in-Docker Feature](../docker-in-docker), the only way to work around this is to use the **host**'s folder paths instead of the container's paths. There are 2 ways to do this
### 1. Use the `${localWorkspaceFolder}` as environment variable in your code
1. Add the following to `devcontainer.json`:
```json
"remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }
```
2. Usage with Docker commands
```bash
docker run -it --rm -v ${LOCAL_WORKSPACE_FOLDER}:/workspace debian bash
```
3. Usage with Docker-compose
```yaml
version: "3.9"
services:
debian:
image: debian
volumes:
- ${LOCAL_WORKSPACE_FOLDER:-./}:/workspace
```
- The defaults value `./` is added so that the `docker-compose.yaml` file can work when it is run outside of the container
### Change the workspace to `${localWorkspaceFolder}`
- This is useful if we don't want to edit the `docker-compose.yaml` file
1. Add the following to `devcontainer.json`
```json
"workspaceFolder": "${localWorkspaceFolder}",
"workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind"
```
2. Rebuild the container.
3. When the container first started with this settings, select the Workspace with the absolute path to the working directory inside the container
4. Docker commands with bind mount should work as they did outside of the devcontainer
> **Note:** There is no `${localWorkspaceFolder}` when using the **Clone Repository in Container Volume** command in the VS Code Dev Containers extension ([info](https://github.com/microsoft/vscode-remote-release/issues/6160#issuecomment-1014701007)).
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/docker-outside-of-docker/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,60 @@
{
"id": "docker-outside-of-docker",
"version": "1.3.1",
"name": "Docker (docker-outside-of-docker)",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker",
"description": "Re-use the host docker socket, adding the Docker CLI to a container. Feature invokes a script to enable using a forwarded Docker socket within a container to run Docker commands.",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"none",
"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": [
"none",
"v1",
"v2"
],
"default": "v2",
"description": "Compose version to use for docker-compose (v1 or v2 or none)"
},
"installDockerBuildx": {
"type": "boolean",
"default": true,
"description": "Install Docker Buildx"
}
},
"entrypoint": "/usr/local/share/docker-init.sh",
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-docker"
]
}
},
"mounts": [
{
"source": "/var/run/docker.sock",
"target": "/var/run/docker-host.sock",
"type": "bind"
}
],
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
],
"legacyIds": [
"docker-from-docker"
]
}

View file

@ -0,0 +1,351 @@
#!/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
DOCKER_VERSION="${VERSION:-"latest"}"
USE_MOBY="${MOBY:-"true"}"
DOCKER_DASH_COMPOSE_VERSION="${DOCKERDASHCOMPOSEVERSION:-"v1"}" # v1 or v2 or none
ENABLE_NONROOT_DOCKER="${ENABLE_NONROOT_DOCKER:-"true"}"
SOURCE_SOCKET="${SOURCE_SOCKET:-"/var/run/docker-host.sock"}"
TARGET_SOCKET="${TARGET_SOCKET:-"/var/run/docker.sock"}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
INSTALL_DOCKER_BUILDX="${INSTALLDOCKERBUILDX:-"true"}"
MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc"
DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="bookworm buster bullseye bionic focal jammy"
DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="bookworm buster bullseye bionic focal hirsute impish jammy"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
# 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 wget
if ! type git > /dev/null 2>&1; then
check_packages 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 supported
if [ "${USE_MOBY}" = "true" ]; then
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
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
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
buildx=()
if [ "${INSTALL_DOCKER_BUILDX}" = "true" ]; then
buildx=(moby-buildx)
fi
apt-get -y install --no-install-recommends ${cli_package_name}${cli_version_suffix} "${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
buildx=()
if [ "${INSTALL_DOCKER_BUILDX}" = "true" ]; then
buildx=(docker-buildx-plugin)
fi
apt-get -y install --no-install-recommends ${cli_package_name}${cli_version_suffix} "${buildx[@]}" docker-compose-plugin
buildx_path="/usr/libexec/docker/cli-plugins/docker-buildx"
# Older versions of Docker CE installs buildx as part of the CLI package
if [ "${INSTALL_DOCKER_BUILDX}" = "false" ] && [ -f "${buildx_path}" ]; then
echo "(*) Removing docker-buildx installed from docker-ce-cli since installDockerBuildx is disabled..."
rm -f "${buildx_path}"
fi
fi
unset buildx buildx_path
fi
# If 'docker-compose' command is to be included
if [ "${DOCKER_DASH_COMPOSE_VERSION}" != "none" ]; then
# 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."
elif [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then
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 runs on this architecture
check_packages python3-minimal python3-pip libffi-dev python3-venv
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
else
echo "(*) Installing compose-switch as docker-compose..."
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/docker-compose
chmod +x /usr/local/bin/docker-compose
# TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11
fi
fi
# Setup a docker group in the event the docker socket's group is not root
if ! grep -qE '^docker:' /etc/group; then
echo "(*) Creating missing docker group..."
groupadd --system docker
fi
# Remarking this out to restore functionality in Azure VMs. ID 999 is a reserved group ID
# Ensure docker group gid is 999
# if [ "$(getent group docker | cut -d: -f3)" != "999" ]; then
# echo "(*) Updating docker group gid to 999..."
# groupmod -g 999 docker
# fi
usermod -aG docker "${USERNAME}"
# If init file already exists, exit
if [ -f "/usr/local/share/docker-init.sh" ]; then
# Clean up
rm -rf /var/lib/apt/lists/*
exit 0
fi
echo "docker-init doesn't 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
# Clean up
rm -rf /var/lib/apt/lists/*
exit 0
fi
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}"
check_packages socat
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
# Clean up
rm -rf /var/lib/apt/lists/*
echo "Done!"

View file

@ -0,0 +1,7 @@
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,32 @@
# Git Large File Support (LFS) (git-lfs)
Installs Git Large File Support (Git LFS) along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like git and curl.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/git-lfs:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select version of Git LFS to install | string | latest |
| autoPull | Automatically pull LFS files when creating the container. When false, running 'git lfs pull' in the container will have the same effect. | boolean | true |
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/git-lfs/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,27 @@
{
"id": "git-lfs",
"version": "1.1.1",
"name": "Git Large File Support (LFS)",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/git-lfs",
"description": "Installs Git Large File Support (Git LFS) along with needed dependencies. Useful for base Dockerfiles that often are missing required install dependencies like git and curl.",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"none"
],
"default": "latest",
"description": "Select version of Git LFS to install"
},
"autoPull": {
"type": "boolean",
"default": true,
"description": "Automatically pull LFS files when creating the container. When false, running 'git lfs pull' in the container will have the same effect."
}
},
"postCreateCommand": "/usr/local/share/pull-git-lfs-artifacts.sh",
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

224
features/src/git-lfs/install.sh Executable file
View file

@ -0,0 +1,224 @@
#!/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
GIT_LFS_VERSION=${VERSION:-"latest"}
AUTO_PULL=${AUTOPULL:="true"}
GIT_LFS_ARCHIVE_GPG_KEY_URI="https://packagecloud.io/github/git-lfs/gpgkey"
GIT_LFS_ARCHIVE_ARCHITECTURES="amd64 arm64"
GIT_LFS_ARCHIVE_VERSION_CODENAMES="stretch buster bullseye bionic focal jammy"
GIT_LFS_CHECKSUM_GPG_KEYS="0x88ace9b29196305ba9947552f1ba225c0223b187 0x86cd3297749375bcf8206715f54fe648088335a9 0xaa3b3450295830d2de6db90caba67be5a5795889"
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com
keyserver hkp://keyserver.ubuntu.com:80
keyserver hkps://keys.openpgp.org
keyserver hkp://keyserver.pgp.com"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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}"
}
# Import the specified key in a variable name passed in as
receive_gpg_keys() {
local keys=${!1}
# Use a temporary location 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
}
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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_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
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
echo "Could not fetch git-lfs from apt"
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"
echo "Looking for release artfact: ${git_lfs_filename}"
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
echo "Validated release artifact integrity."
echo "Starting to extract..."
tar xf "${git_lfs_filename}" -C .
echo "Installing..."
if [ -f "./install.sh" ]; then
./install.sh
else
# Starting around v3.2.0, the release
# artifact file structure changed slightly
enclosed_folder="git-lfs-${GIT_LFS_VERSION}"
cd ${enclosed_folder}
./install.sh
cd ../
fi
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
check_packages 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
# --- Generate a 'pull-git-lfs-artifacts.sh' script to be executed by the 'postCreateCommand' lifecycle hook
PULL_GIT_LFS_SCRIPT_PATH="/usr/local/share/pull-git-lfs-artifacts.sh"
tee "$PULL_GIT_LFS_SCRIPT_PATH" > /dev/null \
<< EOF
#!/bin/sh
set -e
AUTO_PULL=${AUTO_PULL}
EOF
tee -a "$PULL_GIT_LFS_SCRIPT_PATH" > /dev/null \
<< 'EOF'
echo "Fetching git lfs artifacts..."
if [ "${AUTO_PULL}" != "true" ]; then
echo "(!) Skipping 'git lfs pull' because 'autoPull' is not set to 'true'"
exit 0
fi
# Check if repo is a git lfs repo.
if ! git lfs ls-files > /dev/null 2>&1; then
echo "(!) Skipping automatic 'git lfs pull' because no git lfs files were detected"
exit 0
fi
git lfs pull
EOF
chmod 755 "$PULL_GIT_LFS_SCRIPT_PATH"
# Clean up
rm -rf /var/lib/apt/lists/*
echo "Done!"

View file

@ -0,0 +1,7 @@
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,32 @@
# Git (from source) (git)
Install an up-to-date version of Git, built from source as needed. Useful for when you want the latest and greatest features. Auto-detects latest stable version and installs needed dependencies.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/git:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a Git version. | string | os-provided |
| ppa | Install from PPA if available | boolean | true |
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/git/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,26 @@
{
"id": "git",
"version": "1.1.6",
"name": "Git (from source)",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/git",
"description": "Install an up-to-date version of Git, built from source as needed. Useful for when you want the latest and greatest features. Auto-detects latest stable version and installs needed dependencies.",
"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"
}
},
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

151
features/src/git/install.sh Executable file
View file

@ -0,0 +1,151 @@
#!/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
GIT_VERSION=${VERSION} # 'system' checks the base image first, else installs 'latest'
USE_PPA_IF_AVAILABLE=${PPA}
GIT_CORE_PPA_ARCHIVE_GPG_KEY=E1DD270288B4E6030699E45FA1715D88E1DF1F24
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com
keyserver hkp://keyserver.ubuntu.com:80
keyserver hkps://keys.openpgp.org
keyserver hkp://keyserver.pgp.com"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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
# Import the specified key in a variable name passed in as
receive_gpg_keys() {
local keys=${!1}
local keyring_args=""
if [ ! -z "$2" ]; then
mkdir -p "$(dirname \"$2\")"
keyring_args="--no-default-keyring --keyring $2"
fi
# Use a temporary location 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
}
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
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)"
# Clean up
rm -rf /var/lib/apt/lists/*
exit 0
fi
echo "Installing git from OS apt repository"
check_packages git
# Clean up
rm -rf /var/lib/apt/lists/*
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"
rm -rf /var/lib/apt/lists/*
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
check_packages libpcre2-dev
if [ "${VERSION_CODENAME}" = "focal" ] || [ "${VERSION_CODENAME}" = "bullseye" ]; then
check_packages libpcre2-posix2
elif [ "${VERSION_CODENAME}" = "bionic" ] || [ "${VERSION_CODENAME}" = "buster" ]; then
check_packages libpcre2-posix0
else
check_packages libpcre2-posix3
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 USE_LIBPCRE=YesPlease prefix=/usr/local sysconfdir=/etc all && make -s USE_LIBPCRE=YesPlease prefix=/usr/local sysconfdir=/etc install 2>&1
rm -rf /tmp/git-${GIT_VERSION}
rm -rf /var/lib/apt/lists/*
echo "Done!"

7
features/src/go/NOTES.md Normal file
View file

@ -0,0 +1,7 @@
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

38
features/src/go/README.md Normal file
View file

@ -0,0 +1,38 @@
# Go (go)
Installs Go and common Go utilities. Auto-detects latest version and installs needed dependencies.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/go:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a Go version to install | string | latest |
| golangciLintVersion | Version of golangci-lint to install | string | latest |
## Customizations
### VS Code Extensions
- `golang.Go`
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/go/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,47 @@
{
"id": "go",
"version": "1.2.2",
"name": "Go",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/go",
"description": "Installs Go and common Go utilities. Auto-detects latest version and installs needed dependencies.",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"none",
"1.21",
"1.20"
],
"default": "latest",
"description": "Select or enter a Go version to install"
},
"golangciLintVersion": {
"type": "string",
"default": "latest",
"description": "Version of golangci-lint to install"
}
},
"init": true,
"customizations": {
"vscode": {
"extensions": [
"golang.Go"
]
}
},
"containerEnv": {
"GOROOT": "/usr/local/go",
"GOPATH": "/go",
"PATH": "/usr/local/go/bin:/go/bin:${PATH}"
},
"capAdd": [
"SYS_PTRACE"
],
"securityOpt": [
"seccomp=unconfined"
],
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

235
features/src/go/install.sh Executable file
View file

@ -0,0 +1,235 @@
#!/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
TARGET_GO_VERSION="${VERSION:-"latest"}"
GOLANGCILINT_VERSION="${GOLANGCILINTVERSION:-"latest"}"
TARGET_GOROOT="${TARGET_GOROOT:-"/usr/local/go"}"
TARGET_GOPATH="${TARGET_GOPATH:-"/go"}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
INSTALL_GO_TOOLS="${INSTALL_GO_TOOLS:-"true"}"
# https://www.google.com/linuxrepositories/
GO_GPG_KEY_URI="https://dl.google.com/linux/linux_signing_key.pub"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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
# 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}"
}
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
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
check_packages 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" ]] && [[ "$(go version)" != *"${TARGET_GO_VERSION}"* ]]; then
# Use a temporary location for gpg keys to avoid polluting image
export GNUPGHOME="/tmp/tmp-gnupg"
mkdir -p ${GNUPGHOME}
chmod 700 ${GNUPGHOME}
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 is already installed with version ${TARGET_GO_VERSION}. Skipping."
fi
# Install Go tools that are isImportant && !replacedByGopls based on
# https://github.com/golang/vscode-go/blob/v0.38.0/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/go-delve/delve/cmd/dlv@latest \
github.com/fatih/gomodifytags@latest \
github.com/haya14busa/goplay/cmd/goplay@latest \
github.com/cweill/gotests/gotests@latest \
github.com/josharian/impl@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
if [ -d /tmp/gotools/bin ]; then
mv /tmp/gotools/bin/* ${TARGET_GOPATH}/bin/
rm -rf /tmp/gotools
fi
# Install golangci-lint from precompiled binares
if [ "$GOLANGCILINT_VERSION" = "latest" ] || [ "$GOLANGCILINT_VERSION" = "" ]; then
echo "Installing golangci-lint latest..."
curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
sh -s -- -b "${TARGET_GOPATH}/bin"
else
echo "Installing golangci-lint ${GOLANGCILINT_VERSION}..."
curl -fsSL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
sh -s -- -b "${TARGET_GOPATH}/bin" "v${GOLANGCILINT_VERSION}"
fi
fi
chown -R "${USERNAME}:golang" "${TARGET_GOROOT}" "${TARGET_GOPATH}"
chmod -R g+r+w "${TARGET_GOROOT}" "${TARGET_GOPATH}"
find "${TARGET_GOROOT}" -type d -print0 | xargs -n 1 -0 chmod g+s
find "${TARGET_GOPATH}" -type d -print0 | xargs -n 1 -0 chmod g+s
# Clean up
rm -rf /var/lib/apt/lists/*
echo "Done!"

View file

@ -0,0 +1,7 @@
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,32 @@
# Hugo (hugo)
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/hugo:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a version. | string | latest |
| extended | Install Hugo extended for SASS/SCSS changes | boolean | false |
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/hugo/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,28 @@
{
"id": "hugo",
"version": "1.1.2",
"name": "Hugo",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/hugo",
"options": {
"version": {
"type": "string",
"proposals": [
"latest"
],
"default": "latest",
"description": "Select or enter a version."
},
"extended": {
"type": "boolean",
"default": false,
"description": "Install Hugo extended for SASS/SCSS changes"
}
},
"containerEnv": {
"HUGO_DIR": "/usr/local/hugo",
"PATH": "/usr/local/hugo/bin:${PATH}"
},
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

132
features/src/hugo/install.sh Executable file
View file

@ -0,0 +1,132 @@
#!/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
VERSION="${VERSION:-"latest"}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
UPDATE_RC="${UPDATE_RC:-"true"}"
HUGO_DIR="${HUGO_DIR:-"/usr/local/hugo"}"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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
}
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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 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
if ! cat /etc/group | grep -e "^hugo:" > /dev/null 2>&1; then
groupadd -r hugo
fi
usermod -a -G hugo "${USERNAME}"
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
# Install extended version of hugo if desired
if [ "${EXTENDED}" = "true" ]; then
extended="extended_"
else
extended=""
fi
hugo_filename="hugo_${extended}${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}"
chown -R "${USERNAME}:hugo" "${HUGO_DIR}"
chmod -R g+r+w "${HUGO_DIR}"
find "${HUGO_DIR}" -type d -print0 | xargs -n 1 -0 chmod g+s
fi
# Clean up
rm -rf /var/lib/apt/lists/*
echo "Done!"

View file

@ -0,0 +1,19 @@
## Ingress and port forwarding
When configuring [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) for your Kubernetes cluster, note that by default Kubernetes will bind to a specific interface's IP rather than localhost or all interfaces. This is why you need to use the Kubernetes Node's IP when connecting - even if there's only one Node as in the case of Minikube. Port forwarding in Remote - Containers will allow you to specify `<ip>:<port>` in either the `forwardPorts` property or through the port forwarding UI in VS Code.
However, GitHub Codespaces does not yet support this capability, so you'll need to use `kubectl` to forward the port to localhost. This adds minimal overhead since everything is on the same machine. E.g.:
```bash
minikube start
minikube addons enable ingress
# Run this to forward to localhost in the background
nohup kubectl port-forward --pod-running-timeout=24h -n ingress-nginx service/ingress-nginx-controller :80 &
```
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,45 @@
# Kubectl, Helm, and Minikube (kubectl-helm-minikube)
Installs latest version of kubectl, Helm, and optionally minikube. Auto-detects latest versions and installs needed dependencies.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a Kubernetes version to install | string | latest |
| helm | Select or enter a Helm version to install | string | latest |
| minikube | Select or enter a Minikube version to install | string | latest |
## Ingress and port forwarding
When configuring [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) for your Kubernetes cluster, note that by default Kubernetes will bind to a specific interface's IP rather than localhost or all interfaces. This is why you need to use the Kubernetes Node's IP when connecting - even if there's only one Node as in the case of Minikube. Port forwarding in Remote - Containers will allow you to specify `<ip>:<port>` in either the `forwardPorts` property or through the port forwarding UI in VS Code.
However, GitHub Codespaces does not yet support this capability, so you'll need to use `kubectl` to forward the port to localhost. This adds minimal overhead since everything is on the same machine. E.g.:
```bash
minikube start
minikube addons enable ingress
# Run this to forward to localhost in the background
nohup kubectl port-forward --pod-running-timeout=24h -n ingress-nginx service/ingress-nginx-controller :80 &
```
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/kubectl-helm-minikube/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,50 @@
{
"id": "kubectl-helm-minikube",
"version": "1.1.5",
"name": "Kubectl, Helm, and Minikube",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/kubectl-helm-minikube",
"description": "Installs latest version of kubectl, Helm, and optionally minikube. Auto-detects latest versions and installs needed dependencies.",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"none",
"1.23",
"1.22",
"1.21",
"none"
],
"default": "latest",
"description": "Select or enter a Kubernetes version to install"
},
"helm": {
"type": "string",
"proposals": [
"latest",
"none"
],
"default": "latest",
"description": "Select or enter a Helm version to install"
},
"minikube": {
"type": "string",
"proposals": [
"latest",
"none"
],
"default": "latest",
"description": "Select or enter a Minikube version to install"
}
},
"mounts": [
{
"source": "minikube-config",
"target": "/home/vscode/.minikube",
"type": "volume"
}
],
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

View file

@ -0,0 +1,239 @@
#!/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
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
KUBECTL_VERSION="${VERSION:-"latest"}"
HELM_VERSION="${HELM:-"latest"}"
MINIKUBE_VERSION="${MINIKUBE:-"latest"}" # latest is also valid
KUBECTL_SHA256="${KUBECTL_SHA256:-"automatic"}"
HELM_SHA256="${HELM_SHA256:-"automatic"}"
MINIKUBE_SHA256="${MINIKUBE_SHA256:-"automatic"}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
HELM_GPG_KEYS_URI="https://raw.githubusercontent.com/helm/helm/main/KEYS"
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com
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
# 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}"
}
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
# 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
check_packages 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
if [ ${KUBECTL_VERSION} != "none" ]; then
# 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
fi
if [ ${HELM_VERSION} != "none" ]; then
# 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}
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
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
# Clean up
rm -rf /var/lib/apt/lists/*
echo -e "\nDone!"

View file

@ -0,0 +1,25 @@
## Using nvm from postCreateCommand or another lifecycle command
Certain operations like `postCreateCommand` run non-interactive, non-login shells. Unfortunately, `nvm` is really particular that it needs to be "sourced" before it is used, which can only happen automatically with interactive and/or login shells. Fortunately, this is easy to work around:
Just can source the `nvm` startup script before using it:
```json
"postCreateCommand": ". ${NVM_DIR}/nvm.sh && nvm install --lts"
```
Note that typically the default shell in these cases is `sh` not `bash`, so use `. ${NVM_DIR}/nvm.sh` instead of `source ${NVM_DIR}/nvm.sh`.
Alternatively, you can start up an interactive shell which will in turn source `nvm`:
```json
"postCreateCommand": "bash -i -c 'nvm install --lts'"
```
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,58 @@
# Node.js (via nvm), yarn and pnpm (node)
Installs Node.js, nvm, yarn, pnpm, and needed dependencies.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/node:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a Node.js version to install | string | lts |
| nodeGypDependencies | Install dependencies to compile native node modules (node-gyp)? | boolean | true |
| nvmInstallPath | The path where NVM will be installed. | string | /usr/local/share/nvm |
| nvmVersion | Version of NVM to install. | string | latest |
## Customizations
### VS Code Extensions
- `dbaeumer.vscode-eslint`
## Using nvm from postCreateCommand or another lifecycle command
Certain operations like `postCreateCommand` run non-interactive, non-login shells. Unfortunately, `nvm` is really particular that it needs to be "sourced" before it is used, which can only happen automatically with interactive and/or login shells. Fortunately, this is easy to work around:
Just can source the `nvm` startup script before using it:
```json
"postCreateCommand": ". ${NVM_DIR}/nvm.sh && nvm install --lts"
```
Note that typically the default shell in these cases is `sh` not `bash`, so use `. ${NVM_DIR}/nvm.sh` instead of `source ${NVM_DIR}/nvm.sh`.
Alternatively, you can start up an interactive shell which will in turn source `nvm`:
```json
"postCreateCommand": "bash -i -c 'nvm install --lts'"
```
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/node/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,56 @@
{
"id": "node",
"version": "1.3.1",
"name": "Node.js (via nvm), yarn and pnpm",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/node",
"description": "Installs Node.js, nvm, yarn, pnpm, and needed dependencies.",
"options": {
"version": {
"type": "string",
"proposals": [
"lts",
"latest",
"none",
"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)?"
},
"nvmInstallPath": {
"type": "string",
"default": "/usr/local/share/nvm",
"description": "The path where NVM will be installed."
},
"nvmVersion": {
"type": "string",
"proposals": [
"latest",
"0.39"
],
"default": "latest",
"description": "Version of NVM to install."
}
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint"
]
}
},
"containerEnv": {
"NVM_DIR": "/usr/local/share/nvm",
"NVM_SYMLINK_CURRENT": "true",
"PATH": "/usr/local/share/nvm/current/bin:${PATH}"
},
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

273
features/src/node/install.sh Executable file
View file

@ -0,0 +1,273 @@
#!/bin/bash
#-------------------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://github.com/devcontainers/features/blob/main/LICENSE for license information.
#-------------------------------------------------------------------------------------------------------------------------
#
# Docs: https://github.com/devcontainers/features/tree/main/src/node
# Maintainer: The Dev Container spec maintainers
export NODE_VERSION="${VERSION:-"lts"}"
export NVM_VERSION="${NVMVERSION:-"latest"}"
export NVM_DIR="${NVMINSTALLPATH:-"/usr/local/share/nvm"}"
INSTALL_TOOLS_FOR_NODE_GYP="${NODEGYPDEPENDENCIES:-true}"
# Comma-separated list of node versions to be installed (with nvm)
# alongside NODE_VERSION, but not set as default.
ADDITIONAL_VERSIONS="${ADDITIONALVERSIONS:-""}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
UPDATE_RC="${UPDATE_RC:-"true"}"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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
}
apt_get_update() {
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
# 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
. /etc/os-release
if [[ "bionic" = *"${VERSION_CODENAME}"* ]]; then
if [[ "${NODE_VERSION}" =~ "18" ]] || [[ "${NODE_VERSION}" = "lts" ]]; then
echo "(!) Unsupported distribution version '${VERSION_CODENAME}' for Node 18. Details: https://github.com/nodejs/node/issues/42351#issuecomment-1068424442"
exit 1
fi
fi
# Install dependencies
check_packages apt-transport-https curl ca-certificates tar gnupg2 dirmngr
if ! type git > /dev/null 2>&1; then
check_packages git
fi
# 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/*"
elif [ "${NODE_VERSION}" = "latest" ]; then
export NODE_VERSION="node"
fi
find_version_from_git_tags NVM_VERSION "https://github.com/nvm-sh/nvm"
# Install snipppet that we will run as the user
nvm_install_snippet="$(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
EOF
)"
# Snippet that should be added into rc / profiles
nvm_rc_snippet="$(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
)"
# Create a symlink to the installed version for use in Dockerfile PATH statements
export NVM_SYMLINK_CURRENT=true
# Create nvm group to the user's UID or GID to change while still allowing access to nvm
if ! cat /etc/group | grep -e "^nvm:" > /dev/null 2>&1; then
groupadd -r nvm
fi
usermod -a -G nvm ${USERNAME}
# Install nvm (which also installs NODE_VERSION), otherwise
# use nvm to install the specified node version. Always use
# umask 0002 so both the owner so that everything is u+rw,g+rw
umask 0002
if [ ! -d "${NVM_DIR}" ]; then
# Create nvm dir, and set sticky bit
mkdir -p "${NVM_DIR}"
chown "${USERNAME}:nvm" "${NVM_DIR}"
chmod g+rws "${NVM_DIR}"
su ${USERNAME} -c "${nvm_install_snippet}" 2>&1
# Update rc files
if [ "${UPDATE_RC}" = "true" ]; then
updaterc "${nvm_rc_snippet}"
fi
else
echo "NVM already installed."
if [ "${NODE_VERSION}" != "" ]; then
su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm install '${NODE_VERSION}' && nvm alias default '${NODE_VERSION}'"
fi
fi
# Additional node versions to be installed but not be set as
# default we can assume the nvm is the group owner of the nvm
# directory and the sticky bit on directories so any installed
# files will have will have the correct ownership (nvm)
if [ ! -z "${ADDITIONAL_VERSIONS}" ]; then
OLDIFS=$IFS
IFS=","
read -a additional_versions <<< "$ADDITIONAL_VERSIONS"
for ver in "${additional_versions[@]}"; do
su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm install '${ver}'"
done
# Ensure $NODE_VERSION is on the $PATH
if [ "${NODE_VERSION}" != "" ]; then
su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm use default"
fi
IFS=$OLDIFS
fi
# Install pnpm
if type pnpm > /dev/null 2>&1; then
echo "pnpm already installed."
else
if type npm > /dev/null 2>&1; then
[ ! -z "$http_proxy" ] && npm set proxy="$http_proxy"
[ ! -z "$https_proxy" ] && npm set https-proxy="$https_proxy"
[ ! -z "$no_proxy" ] && npm set noproxy="$no_proxy"
npm install -g pnpm
else
echo "Skip installing pnpm because npm is missing"
fi
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
apt-get -y install ${to_install}
fi
fi
# Clean up
su ${USERNAME} -c "umask 0002 && . '$NVM_DIR/nvm.sh' && nvm clear-cache"
rm -rf /var/lib/apt/lists/*
# Ensure privs are correct for installed node versions. Unfortunately the
# way nvm installs node versions pulls privs from the tar which does not
# have group write set. We need this when the gid/uid is updated.
mkdir -p "${NVM_DIR}/versions"
chmod -R g+rw "${NVM_DIR}/versions"
echo "Done!"

View file

@ -0,0 +1,7 @@
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,41 @@
# PHP (php)
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/php:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a PHP version | string | latest |
| installComposer | Install PHP Composer? | boolean | true |
## Customizations
### VS Code Extensions
- `xdebug.php-debug`
- `bmewburn.vscode-intelephense-client`
- `xdebug.php-pack`
- `devsense.phptools-vscode`
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/php/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,42 @@
{
"id": "php",
"version": "1.1.2",
"name": "PHP",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/php",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"8",
"8.2",
"8.2.0",
"none"
],
"default": "latest",
"description": "Select or enter a PHP version"
},
"installComposer": {
"type": "boolean",
"default": true,
"description": "Install PHP Composer?"
}
},
"customizations": {
"vscode": {
"extensions": [
"xdebug.php-debug",
"bmewburn.vscode-intelephense-client",
"xdebug.php-pack",
"devsense.phptools-vscode"
]
}
},
"containerEnv": {
"PHP_PATH": "/usr/local/php/current",
"PATH": "/usr/local/php/current/bin:${PATH}"
},
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

273
features/src/php/install.sh Executable file
View file

@ -0,0 +1,273 @@
#!/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.
#-------------------------------------------------------------------------------------------------------------
#
# Maintainer: The VS Code and Codespaces Teams
set -eux
# Clean up
rm -rf /var/lib/apt/lists/*
PHP_VERSION="${VERSION:-"latest"}"
INSTALL_COMPOSER="${INSTALLCOMPOSER:-"true"}"
OVERRIDE_DEFAULT_VERSION="${OVERRIDEDEFAULTVERSION:-"true"}"
export PHP_DIR="${PHP_DIR:-"/usr/local/php"}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
UPDATE_RC="${UPDATE_RC:-"true"}"
# Comma-separated list of php versions to be installed
# alongside PHP_VERSION, but not set as default.
ADDITIONAL_VERSIONS="${ADDITIONALVERSIONS:-""}"
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
# 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=root
fi
elif [ "${USERNAME}" = "none" ]; then
USERNAME=root
USER_UID=0
USER_GID=0
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
}
# Checks if packages are installed and installs them if not
check_packages() {
if ! dpkg -s "$@" > /dev/null 2>&1; then
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
fi
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"}
echo "${!variable_name}"
echo "$(echo "${requested_version}" | grep -o "." | wc -l)"
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
echo "${!variable_name}"
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}"
}
# Install PHP Composer
addcomposer() {
"${PHP_SRC}" -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
HASH="$(wget -q -O - https://composer.github.io/installer.sig)"
"${PHP_SRC}" -r "if (hash_file('sha384', 'composer-setup.php') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
"${PHP_SRC}" composer-setup.php --install-dir="/usr/local/bin" --filename=composer
"${PHP_SRC}" -r "unlink('composer-setup.php');"
}
install_php() {
PHP_VERSION="$1"
PHP_INSTALL_DIR="${PHP_DIR}/${PHP_VERSION}"
if [ -d "${PHP_INSTALL_DIR}" ]; then
echo "(!) PHP version ${PHP_VERSION} already exists."
exit 1
fi
if ! cat /etc/group | grep -e "^php:" > /dev/null 2>&1; then
groupadd -r php
fi
usermod -a -G php "${USERNAME}"
PHP_URL="https://www.php.net/distributions/php-${PHP_VERSION}.tar.gz"
PHP_INI_DIR="${PHP_INSTALL_DIR}/ini"
CONF_DIR="${PHP_INI_DIR}/conf.d"
mkdir -p "${CONF_DIR}";
PHP_EXT_DIR="${PHP_INSTALL_DIR}/extensions"
mkdir -p "${PHP_EXT_DIR}"
PHP_SRC_DIR="/usr/src/php"
mkdir -p $PHP_SRC_DIR
cd $PHP_SRC_DIR
wget -O php.tar.xz "$PHP_URL"
tar -xf $PHP_SRC_DIR/php.tar.xz -C "$PHP_SRC_DIR" --strip-components=1
cd $PHP_SRC_DIR;
# PHP 7.4+, the pecl/pear installers are officially deprecated and are removed in PHP 8+
# Thus, requiring an explicit "--with-pear"
IFS="."
read -a versions <<< "${PHP_VERSION}"
PHP_MAJOR_VERSION=${versions[0]}
PHP_MINOR_VERSION=${versions[1]}
VERSION_CONFIG=""
if (( $(($PHP_MAJOR_VERSION)) >= 8 )) || (( $(($PHP_MAJOR_VERSION)) == 7 && $(($PHP_MINOR_VERSION)) >= 4 )); then
VERSION_CONFIG="--with-pear"
fi
./configure --prefix="${PHP_INSTALL_DIR}" --with-config-file-path="$PHP_INI_DIR" --with-config-file-scan-dir="$CONF_DIR" --enable-option-checking=fatal --with-curl --with-libedit --enable-mbstring --with-openssl --with-zlib --with-password-argon2 --with-sodium=shared "$VERSION_CONFIG" EXTENSION_DIR="$PHP_EXT_DIR";
make -j "$(nproc)"
find -type f -name '*.a' -delete
make install
find "${PHP_INSTALL_DIR}" -type f -executable -exec strip --strip-all '{}' + || true
make clean
cp -v $PHP_SRC_DIR/php.ini-* "$PHP_INI_DIR/";
cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# Install xdebug
"${PHP_INSTALL_DIR}/bin/pecl" install xdebug
XDEBUG_INI="${CONF_DIR}/xdebug.ini"
echo "zend_extension=${PHP_EXT_DIR}/xdebug.so" > "${XDEBUG_INI}"
echo "xdebug.mode = debug" >> "${XDEBUG_INI}"
echo "xdebug.start_with_request = yes" >> "${XDEBUG_INI}"
echo "xdebug.client_port = 9003" >> "${XDEBUG_INI}"
}
if [ "${PHP_VERSION}" != "none" ]; then
# Persistent / runtime dependencies
RUNTIME_DEPS="wget ca-certificates git build-essential xz-utils"
# PHP dependencies
PHP_DEPS="libssl-dev libcurl4-openssl-dev libedit-dev libsqlite3-dev libxml2-dev zlib1g-dev libsodium-dev libonig-dev"
. /etc/os-release
if [ "${VERSION_CODENAME}" = "bionic" ]; then
PHP_DEPS="${PHP_DEPS} libargon2-0-dev"
else
PHP_DEPS="${PHP_DEPS} libargon2-dev"
fi
# Dependencies required for running "phpize"
PHPIZE_DEPS="autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c"
# Install dependencies
check_packages $RUNTIME_DEPS $PHP_DEPS $PHPIZE_DEPS
find_version_from_git_tags PHP_VERSION https://github.com/php/php-src "tags/php-"
install_php "${PHP_VERSION}"
PHP_SRC="${PHP_INSTALL_DIR}/bin/php"
else
set +e
PHP_SRC=$(which php)
set -e
fi
# Install PHP Composer if needed
if [[ "${INSTALL_COMPOSER}" = "true" ]]; then
if [ -z "${PHP_SRC}" ]; then
echo "(!) Could not install Composer. PHP not found."
exit 1
fi
addcomposer
fi
# Additional php versions to be installed but not be set as default.
if [ ! -z "${ADDITIONAL_VERSIONS}" ]; then
OLDIFS=$IFS
IFS=","
read -a additional_versions <<< "$ADDITIONAL_VERSIONS"
for version in "${additional_versions[@]}"; do
OVERRIDE_DEFAULT_VERSION="false"
install_php "${version}"
done
IFS=$OLDIFS
fi
if [ "${PHP_VERSION}" != "none" ]; then
CURRENT_DIR="${PHP_DIR}/current"
if [[ ! -d "${CURRENT_DIR}" ]]; then
ln -s -r "${PHP_INSTALL_DIR}" ${CURRENT_DIR}
fi
if [ "${OVERRIDE_DEFAULT_VERSION}" = "true" ]; then
if [[ $(ls -l ${CURRENT_DIR}) != *"-> ${PHP_INSTALL_DIR}"* ]] ; then
rm "${CURRENT_DIR}"
ln -s -r "${PHP_INSTALL_DIR}" "${CURRENT_DIR}"
fi
fi
rm -rf "${PHP_SRC_DIR}"
updaterc "if [[ \"\${PATH}\" != *\"${CURRENT_DIR}\"* ]]; then export PATH=\"${CURRENT_DIR}/bin:\${PATH}\"; fi"
chown -R "${USERNAME}:php" "${PHP_DIR}"
chmod -R g+r+w "${PHP_DIR}"
find "${PHP_DIR}" -type d -print0 | xargs -n 1 -0 chmod g+s
fi
# Clean up
rm -rf /var/lib/apt/lists/*
echo "Done!"

View file

@ -0,0 +1,7 @@
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,44 @@
# Python (python)
Installs the provided version of Python, as well as PIPX, and other common Python utilities. JupyterLab is conditionally installed with the python feature. Note: May require source code compilation.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/python:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select a Python version to install. | string | os-provided |
| installTools | Install common Python tools like pylint | boolean | true |
| optimize | Optimize Python for performance when compiled (slow) | boolean | false |
| installPath | The path where python will be installed. | string | /usr/local/python |
| installJupyterlab | Install JupyterLab, a web-based interactive development environment for notebooks | boolean | false |
| configureJupyterlabAllowOrigin | Configure JupyterLab to accept HTTP requests from the specified origin | string | - |
| httpProxy | Connect to GPG keyservers using a proxy for fetching source code signatures by configuring this option | string | - |
## Customizations
### VS Code Extensions
- `ms-python.python`
- `ms-python.vscode-pylance`
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/python/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,77 @@
{
"id": "python",
"version": "1.3.1",
"name": "Python",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/python",
"description": "Installs the provided version of Python, as well as PIPX, and other common Python utilities. JupyterLab is conditionally installed with the python feature. Note: May require source code compilation.",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"os-provided",
"none",
"3.12",
"3.11",
"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)"
},
"installPath": {
"type": "string",
"default": "/usr/local/python",
"description": "The path where python will be installed."
},
"installJupyterlab": {
"type": "boolean",
"default": false,
"description": "Install JupyterLab, a web-based interactive development environment for notebooks"
},
"configureJupyterlabAllowOrigin": {
"type": "string",
"default": "",
"description": "Configure JupyterLab to accept HTTP requests from the specified origin"
},
"httpProxy": {
"type": "string",
"default": "",
"description": "Connect to GPG keyservers using a proxy for fetching source code signatures by configuring this option"
}
},
"containerEnv": {
"PYTHON_PATH": "/usr/local/python/current",
"PIPX_HOME": "/usr/local/py-utils",
"PIPX_BIN_DIR": "/usr/local/py-utils/bin",
"PATH": "/usr/local/python/current/bin:/usr/local/py-utils/bin:${PATH}"
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/python/current/bin/python"
}
}
},
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz",
"ghcr.io/devcontainers/features/oryx"
]
}

497
features/src/python/install.sh Executable file
View file

@ -0,0 +1,497 @@
#!/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/python.md
# Maintainer: The VS Code and Codespaces Teams
PYTHON_VERSION="${VERSION:-"latest"}" # 'system' or 'os-provided' checks the base image first, else installs 'latest'
INSTALL_PYTHON_TOOLS="${INSTALLTOOLS:-"true"}"
OPTIMIZE_BUILD_FROM_SOURCE="${OPTIMIZE:-"false"}"
PYTHON_INSTALL_PATH="${INSTALLPATH:-"/usr/local/python"}"
OVERRIDE_DEFAULT_VERSION="${OVERRIDEDEFAULTVERSION:-"true"}"
export PIPX_HOME=${PIPX_HOME:-"/usr/local/py-utils"}
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
UPDATE_RC="${UPDATE_RC:-"true"}"
USE_ORYX_IF_AVAILABLE="${USEORYXIFAVAILABLE:-"true"}"
INSTALL_JUPYTERLAB="${INSTALLJUPYTERLAB:-"false"}"
CONFIGURE_JUPYTERLAB_ALLOW_ORIGIN="${CONFIGUREJUPYTERLABALLOWORIGIN:-""}"
# Comma-separated list of python versions to be installed
# alongside PYTHON_VERSION, but not set as default.
ADDITIONAL_VERSIONS="${ADDITIONALVERSIONS:-""}"
DEFAULT_UTILS=("pylint" "flake8" "autopep8" "black" "yapf" "mypy" "pydocstyle" "pycodestyle" "bandit" "pipenv" "virtualenv" "pytest")
PYTHON_SOURCE_GPG_KEYS="64E628F8D684696D B26995E310250568 2D347EA6AA65421D FB9921286F5E1540 3A5CA953F73C700D 04C367C218ADD4FF 0EDDC5F26A45C816 6AF053F07D9DC8D2 C9BE28DEE6DF025C 126EB563A74B06BF D9866941EA5BBD71 ED9D77D5 A821E680E5FA6305"
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com
keyserver hkp://keyserver.ubuntu.com:80
keyserver hkps://keys.openpgp.org
keyserver hkp://keyserver.pgp.com"
KEYSERVER_PROXY="${HTTPPROXY:-"${HTTP_PROXY:-""}"}"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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
}
# Import the specified key in a variable name passed in as
receive_gpg_keys() {
local keys=${!1}
local keyring_args=""
if [ ! -z "$2" ]; then
mkdir -p "$(dirname \"$2\")"
keyring_args="--no-default-keyring --keyring $2"
fi
if [ ! -z "${KEYSERVER_PROXY}" ]; then
keyring_args="${keyring_args} --keyserver-options http-proxy=${KEYSERVER_PROXY}"
fi
# Use a temporary location 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
}
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
add_symlink() {
if [[ ! -d "${CURRENT_PATH}" ]]; then
ln -s -r "${INSTALL_PATH}" "${CURRENT_PATH}"
fi
if [ "${OVERRIDE_DEFAULT_VERSION}" = "true" ]; then
if [[ $(ls -l ${CURRENT_PATH}) != *"-> ${INSTALL_PATH}"* ]] ; then
rm "${CURRENT_PATH}"
ln -s -r "${INSTALL_PATH}" "${CURRENT_PATH}"
fi
fi
}
install_from_source() {
VERSION=$1
echo "(*) Building 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
check_packages git
fi
# Find version using soft match
find_version_from_git_tags VERSION "https://github.com/python/cpython"
INSTALL_PATH="${PYTHON_INSTALL_PATH}/${VERSION}"
if [ -d "${INSTALL_PATH}" ]; then
echo "(!) Python version ${VERSION} already exists."
exit 1
fi
# Download tgz of source
mkdir -p /tmp/python-src ${INSTALL_PATH}
cd /tmp/python-src
local tgz_filename="Python-${VERSION}.tgz"
local tgz_url="https://www.python.org/ftp/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="${INSTALL_PATH}" --with-ensurepip=install ${config_args}
make -j 8
make install
cd /tmp
rm -rf /tmp/python-src ${GNUPGHOME} /tmp/vscdc-settings.env
ln -s "${INSTALL_PATH}/bin/python3" "${INSTALL_PATH}/bin/python"
ln -s "${INSTALL_PATH}/bin/pip3" "${INSTALL_PATH}/bin/pip"
ln -s "${INSTALL_PATH}/bin/idle3" "${INSTALL_PATH}/bin/idle"
ln -s "${INSTALL_PATH}/bin/pydoc3" "${INSTALL_PATH}/bin/pydoc"
ln -s "${INSTALL_PATH}/bin/python3-config" "${INSTALL_PATH}/bin/python-config"
add_symlink
}
install_using_oryx() {
VERSION=$1
INSTALL_PATH="${PYTHON_INSTALL_PATH}/${VERSION}"
if [ -d "${INSTALL_PATH}" ]; then
echo "(!) Python version ${VERSION} already exists."
exit 1
fi
# The python install root path may not exist, so create it
mkdir -p "${PYTHON_INSTALL_PATH}"
oryx_install "python" "${VERSION}" "${INSTALL_PATH}" "lib" || return 1
ln -s "${INSTALL_PATH}/bin/idle3" "${INSTALL_PATH}/bin/idle"
ln -s "${INSTALL_PATH}/bin/pydoc3" "${INSTALL_PATH}/bin/pydoc"
ln -s "${INSTALL_PATH}/bin/python3-config" "${INSTALL_PATH}/bin/python-config"
add_symlink
}
sudo_if() {
COMMAND="$*"
if [ "$(id -u)" -eq 0 ] && [ "$USERNAME" != "root" ]; then
su - "$USERNAME" -c "$COMMAND"
else
$COMMAND
fi
}
install_user_package() {
INSTALL_UNDER_ROOT="$1"
PACKAGE="$2"
if [ "$INSTALL_UNDER_ROOT" = true ]; then
sudo_if "${PYTHON_SRC}" -m pip install --upgrade --no-cache-dir "$PACKAGE"
else
sudo_if "${PYTHON_SRC}" -m pip install --user --upgrade --no-cache-dir "$PACKAGE"
fi
}
add_user_jupyter_config() {
CONFIG_DIR="$1"
CONFIG_FILE="$2"
# Make sure the config file exists or create it with proper permissions
test -d "$CONFIG_DIR" || sudo_if mkdir "$CONFIG_DIR"
test -f "$CONFIG_FILE" || sudo_if touch "$CONFIG_FILE"
# Don't write the same config more than once
grep -q "$3" "$CONFIG_FILE" || echo "$3" >> "$CONFIG_FILE"
}
install_python() {
version=$1
# If the os-provided versions are "good enough", detect that and bail out.
if [ ${version} = "os-provided" ] || [ ${version} = "system" ]; then
check_packages python3 python3-doc python3-pip python3-venv python3-dev python3-tk
INSTALL_PATH="/usr"
local current_bin_path="${CURRENT_PATH}/bin"
if [ "${OVERRIDE_DEFAULT_VERSION}" = "true" ]; then
rm -rf "${current_bin_path}"
fi
if [ ! -d "${current_bin_path}" ] ; then
mkdir -p "${current_bin_path}"
# Add an interpreter symlink but point it to "/usr" since python is at /usr/bin/python, add other alises
ln -s "${INSTALL_PATH}/bin/python3" "${current_bin_path}/python3"
ln -s "${INSTALL_PATH}/bin/python3" "${current_bin_path}/python"
ln -s "${INSTALL_PATH}/bin/pydoc3" "${current_bin_path}/pydoc3"
ln -s "${INSTALL_PATH}/bin/pydoc3" "${current_bin_path}/pydoc"
ln -s "${INSTALL_PATH}/bin/python3-config" "${current_bin_path}/python3-config"
ln -s "${INSTALL_PATH}/bin/python3-config" "${current_bin_path}/python-config"
fi
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 $version || should_install_from_source=true
else
should_install_from_source=true
fi
if [ "${should_install_from_source}" = "true" ]; then
install_from_source $version
fi
}
# 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 ! cat /etc/group | grep -e "^python:" > /dev/null 2>&1; then
groupadd -r python
fi
usermod -a -G python "${USERNAME}"
CURRENT_PATH="${PYTHON_INSTALL_PATH}/current"
install_python ${PYTHON_VERSION}
# Additional python versions to be installed but not be set as default.
if [ ! -z "${ADDITIONAL_VERSIONS}" ]; then
OLD_INSTALL_PATH="${INSTALL_PATH}"
OLDIFS=$IFS
IFS=","
read -a additional_versions <<< "$ADDITIONAL_VERSIONS"
for version in "${additional_versions[@]}"; do
OVERRIDE_DEFAULT_VERSION="false"
install_python $version
done
INSTALL_PATH="${OLD_INSTALL_PATH}"
IFS=$OLDIFS
fi
if [ ${PYTHON_VERSION} != "os-provided" ] && [ ${PYTHON_VERSION} != "system" ]; then
updaterc "if [[ \"\${PATH}\" != *\"${CURRENT_PATH}/bin\"* ]]; then export PATH=${CURRENT_PATH}/bin:\${PATH}; fi"
PATH="${INSTALL_PATH}/bin:${PATH}"
fi
# Updates the symlinks for os-provided, or the installed python version in other cases
chown -R "${USERNAME}:python" "${PYTHON_INSTALL_PATH}"
chmod -R g+r+w "${PYTHON_INSTALL_PATH}"
find "${PYTHON_INSTALL_PATH}" -type d -print0 | xargs -0 -n 1 chmod g+s
PYTHON_SRC="${INSTALL_PATH}/bin/python3"
else
PYTHON_SRC=$(which python)
fi
# Install Python tools if needed
if [[ "${INSTALL_PYTHON_TOOLS}" = "true" ]] && [[ $(python --version) != "" ]]; then
echo 'Installing Python tools...'
export PIPX_BIN_DIR="${PIPX_HOME}/bin"
PATH="${PATH}:${PIPX_BIN_DIR}"
# 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 -R "${USERNAME}:pipx" ${PIPX_HOME}
chmod -R g+r+w "${PIPX_HOME}"
find "${PIPX_HOME}" -type d -print0 | xargs -0 -n 1 chmod g+s
# Update pip if not using os provided python
if [[ $(python --version) != "" ]] || [[ ${PYTHON_VERSION} != "os-provided" ]] && [[ ${PYTHON_VERSION} != "system" ]] && [[ ${PYTHON_VERSION} != "none" ]]; then
echo "Updating pip..."
python -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_DIR=""
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_DIR="/tmp/pip-tmp/bin/"
fi
for util in "${DEFAULT_UTILS[@]}"; do
if ! type ${util} > /dev/null 2>&1; then
"${PIPX_DIR}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
updaterc "export PIPX_HOME=\"${PIPX_HOME}\""
updaterc "export PIPX_BIN_DIR=\"${PIPX_BIN_DIR}\""
updaterc "if [[ \"\${PATH}\" != *\"\${PIPX_BIN_DIR}\"* ]]; then export PATH=\"\${PATH}:\${PIPX_BIN_DIR}\"; fi"
fi
# Install JupyterLab if needed
if [ "${INSTALL_JUPYTERLAB}" = "true" ]; then
if [ -z "${PYTHON_SRC}" ]; then
echo "(!) Could not install Jupyterlab. Python not found."
exit 1
fi
INSTALL_UNDER_ROOT=true
if [ "$(id -u)" -eq 0 ] && [ "$USERNAME" != "root" ]; then
INSTALL_UNDER_ROOT=false
fi
install_user_package $INSTALL_UNDER_ROOT jupyterlab
install_user_package $INSTALL_UNDER_ROOT jupyterlab-git
# Configure JupyterLab if needed
if [ -n "${CONFIGURE_JUPYTERLAB_ALLOW_ORIGIN}" ]; then
# Resolve config directory
CONFIG_DIR="/root/.jupyter"
if [ "$INSTALL_UNDER_ROOT" = false ]; then
CONFIG_DIR="/home/$USERNAME/.jupyter"
fi
CONFIG_FILE="$CONFIG_DIR/jupyter_server_config.py"
add_user_jupyter_config $CONFIG_DIR $CONFIG_FILE "c.ServerApp.allow_origin = '${CONFIGURE_JUPYTERLAB_ALLOW_ORIGIN}'"
add_user_jupyter_config $CONFIG_DIR $CONFIG_FILE "c.NotebookApp.allow_origin = '${CONFIGURE_JUPYTERLAB_ALLOW_ORIGIN}'"
fi
fi
# Clean up
rm -rf /var/lib/apt/lists/*
echo "Done!"

View file

@ -0,0 +1,7 @@
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,41 @@
# Rust (rust)
Installs Rust, common Rust utilities, and their required dependencies
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/rust:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Select or enter a version of Rust to install. | string | latest |
| profile | Select a rustup install profile. | string | minimal |
## Customizations
### VS Code Extensions
- `vadimcn.vscode-lldb`
- `rust-lang.rust-analyzer`
- `tamasfe.even-better-toml`
- `serayuzgur.crates`
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/rust/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,67 @@
{
"id": "rust",
"version": "1.1.1",
"name": "Rust",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/rust",
"description": "Installs Rust, common Rust utilities, and their required dependencies",
"options": {
"version": {
"type": "string",
"proposals": [
"latest",
"none",
"1.70",
"1.69",
"1.68",
"1.67",
"1.66",
"1.65",
"1.64",
"1.63",
"1.62",
"1.61"
],
"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."
}
},
"customizations": {
"vscode": {
"extensions": [
"vadimcn.vscode-lldb",
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"serayuzgur.crates"
],
"settings": {
"files.watcherExclude": {
"**/target/**": true
}
}
}
},
"containerEnv": {
"CARGO_HOME": "/usr/local/cargo",
"RUSTUP_HOME": "/usr/local/rustup",
"PATH": "/usr/local/cargo/bin:${PATH}"
},
"capAdd": [
"SYS_PTRACE"
],
"securityOpt": [
"seccomp=unconfined"
],
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

219
features/src/rust/install.sh Executable file
View file

@ -0,0 +1,219 @@
#!/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
RUST_VERSION="${VERSION:-"latest"}"
RUSTUP_PROFILE="${PROFILE:-"minimal"}"
export CARGO_HOME="${CARGO_HOME:-"/usr/local/cargo"}"
export RUSTUP_HOME="${RUSTUP_HOME:-"/usr/local/rustup"}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
UPDATE_RC="${UPDATE_RC:-"true"}"
UPDATE_RUST="${UPDATE_RUST:-"false"}"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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
# 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}"
}
check_nightly_version_formatting() {
local variable_name=$1
local requested_version=${!variable_name}
if [ "${requested_version}" = "none" ]; then return; fi
local version_date=$(echo ${requested_version} | sed -e "s/^nightly-//")
date -d ${version_date} &>/dev/null
if [ $? != 0 ]; then
echo -e "Invalid ${variable_name} value: ${requested_version}\nNightly version should be in the format nightly-YYYY-MM-DD" >&2
exit 1
fi
if [ $(date -d ${version_date} +%s) -ge $(date +%s) ]; then
echo -e "Invalid ${variable_name} value: ${requested_version}\nNightly version should not exceed current date" >&2
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
}
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
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
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 "${USERNAME}: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
check_packages git
fi
is_nightly=0
echo ${RUST_VERSION} | grep -q "nightly" || is_nightly=$?
if [ $is_nightly = 0 ]; then
check_nightly_version_formatting RUST_VERSION
else
find_version_from_git_tags RUST_VERSION "https://github.com/rust-lang/rust" "tags/"
fi
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}"
# Clean up
rm -rf /var/lib/apt/lists/*
echo "Done!"

View file

@ -0,0 +1,69 @@
## Usage
While the some services automates SSH setup (e.g., when using the GitHub CLI for GitHub Codespaces), this may not be the case for other tools and services. Follow these directions to connect to the dev container from these other tools:
1. Connect to your dev container using a desktop tool or CLI that supports the dev container spec (e.g., VS Code client).
2. The first time you've started the container, you will want to set a password for your user. If running as a user other than root, and you have `sudo` installed:
```bash
sudo passwd $(whoami)
```
Or if you are running as root:
```bash
passwd
```
3. Forward the SSH port (`2222` by default) to your local machine using either the `forwardPorts` property in `devcontainer.json` or the user interface in your tool (e.g., you can press <kbd>F1</kbd> or <kbd>Ctrl/Cmd</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> and select **Ports: Focus on Ports View** in VS Code to bring it into focus).
4. Use a **local terminal** (or other tool) to connect to it using the command and password from step 2. e.g.
```bash
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null vscode@localhost
```
...where `vscode` above is the user you are running as in the container and `2222` after `-p` is the **local address port** from step 2.
The “-o” arguments are optional, but will prevent you from getting warnings or errors about known hosts when you do this from multiple containers/codespaces.
5. Next time you connect to your container, just repeat steps 3 and 4 and use the same password you set in step 2.
### Using SSHFS
[SSHFS](https://en.wikipedia.org/wiki/SSHFS) allows you to mount a remote filesystem to your local machine with nothing but a SSH connection. Here's how to use it with a dev container.
1. Follow the steps in the previous section to ensure you can connect to the dev container using the normal `ssh` client.
2. Install a SSHFS client.
- **Windows:** Install [WinFsp](https://github.com/billziss-gh/winfsp/releases) and [SSHFS-Win](https://github.com/billziss-gh/sshfs-win/releases).
- **macOS**: Use [Homebrew](https://brew.sh/) to install: `brew install macfuse gromgit/fuse/sshfs-mac`
- **Linux:** Use your native package manager to install your distribution's copy of the sshfs package. e.g. `sudo apt-get update && sudo apt-get install sshfs`
3. Mount the remote filesystem.
- **macOS / Linux:** Use the `sshfs` command to mount the remote filesystem. The arguments are similar to the normal `ssh` command but with a few additions. For example:
```
mkdir -p ~/sshfs/devcontainer
sshfs "vscode@localhost:/workspaces" "$HOME/sshfs/devcontainer" -p 2222 -o follow_symlinks -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null -C
```
...where `vscode` above is the user you are running as in the container (e.g. `codespace`, `vscode`, `node`, or `root`) and `2222` after the `-p` is the same local port you used in the `ssh` command in step 1.
- **Windows:** Press Window+R and enter the following in the "Open" field in the Run dialog:
```
\\sshfs.r\vscode@localhost!2222\workspaces
```
...where `vscode` above is the user you are running as in the container and `2222` after the `!` is the same local port you used in the `ssh` command in the previous section.
4. Your dev container's filesystem should now be available in the `~/sshfs/devcontainer` folder on macOS or Linux or in a new explorer window on Windows.
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.

View file

@ -0,0 +1,93 @@
# SSH server (sshd)
Adds a SSH server into a container so that you can use an external terminal, sftp, or SSHFS to interact with it.
## Example Usage
```json
"features": {
"ghcr.io/devcontainers/features/sshd:1": {}
}
```
## Options
| Options Id | Description | Type | Default Value |
|-----|-----|-----|-----|
| version | Currently unused. | string | latest |
## Usage
While the some services automates SSH setup (e.g., when using the GitHub CLI for GitHub Codespaces), this may not be the case for other tools and services. Follow these directions to connect to the dev container from these other tools:
1. Connect to your dev container using a desktop tool or CLI that supports the dev container spec (e.g., VS Code client).
2. The first time you've started the container, you will want to set a password for your user. If running as a user other than root, and you have `sudo` installed:
```bash
sudo passwd $(whoami)
```
Or if you are running as root:
```bash
passwd
```
3. Forward the SSH port (`2222` by default) to your local machine using either the `forwardPorts` property in `devcontainer.json` or the user interface in your tool (e.g., you can press <kbd>F1</kbd> or <kbd>Ctrl/Cmd</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> and select **Ports: Focus on Ports View** in VS Code to bring it into focus).
4. Use a **local terminal** (or other tool) to connect to it using the command and password from step 2. e.g.
```bash
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null vscode@localhost
```
...where `vscode` above is the user you are running as in the container and `2222` after `-p` is the **local address port** from step 2.
The “-o” arguments are optional, but will prevent you from getting warnings or errors about known hosts when you do this from multiple containers/codespaces.
5. Next time you connect to your container, just repeat steps 3 and 4 and use the same password you set in step 2.
### Using SSHFS
[SSHFS](https://en.wikipedia.org/wiki/SSHFS) allows you to mount a remote filesystem to your local machine with nothing but a SSH connection. Here's how to use it with a dev container.
1. Follow the steps in the previous section to ensure you can connect to the dev container using the normal `ssh` client.
2. Install a SSHFS client.
- **Windows:** Install [WinFsp](https://github.com/billziss-gh/winfsp/releases) and [SSHFS-Win](https://github.com/billziss-gh/sshfs-win/releases).
- **macOS**: Use [Homebrew](https://brew.sh/) to install: `brew install macfuse gromgit/fuse/sshfs-mac`
- **Linux:** Use your native package manager to install your distribution's copy of the sshfs package. e.g. `sudo apt-get update && sudo apt-get install sshfs`
3. Mount the remote filesystem.
- **macOS / Linux:** Use the `sshfs` command to mount the remote filesystem. The arguments are similar to the normal `ssh` command but with a few additions. For example:
```
mkdir -p ~/sshfs/devcontainer
sshfs "vscode@localhost:/workspaces" "$HOME/sshfs/devcontainer" -p 2222 -o follow_symlinks -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null -C
```
...where `vscode` above is the user you are running as in the container (e.g. `codespace`, `vscode`, `node`, or `root`) and `2222` after the `-p` is the same local port you used in the `ssh` command in step 1.
- **Windows:** Press Window+R and enter the following in the "Open" field in the Run dialog:
```
\\sshfs.r\vscode@localhost!2222\workspaces
```
...where `vscode` above is the user you are running as in the container and `2222` after the `!` is the same local port you used in the `ssh` command in the previous section.
4. Your dev container's filesystem should now be available in the `~/sshfs/devcontainer` folder on macOS or Linux or in a new explorer window on Windows.
## OS Support
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
`bash` is required to execute the `install.sh` script.
---
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/sshd/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

View file

@ -0,0 +1,21 @@
{
"id": "sshd",
"version": "1.0.9",
"name": "SSH server",
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/sshd",
"description": "Adds a SSH server into a container so that you can use an external terminal, sftp, or SSHFS to interact with it.",
"options": {
"version": {
"type": "string",
"proposals": [
"latest"
],
"default": "latest",
"description": "Currently unused."
}
},
"entrypoint": "/usr/local/share/ssh-init.sh",
"installsAfter": [
"https://gitrepo.ru/api/packages/NeonXP/generic/features/latest/devcontainer-feature-common-utils.tgz"
]
}

139
features/src/sshd/install.sh Executable file
View file

@ -0,0 +1,139 @@
#!/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
#
# Note: You can change your user's password with "sudo passwd $(whoami)" (or just "passwd" if running as root).
SSHD_PORT="${SSHD_PORT:-"2222"}"
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
START_SSHD="${START_SSHD:-"false"}"
NEW_PASSWORD="${NEW_PASSWORD:-"skip"}"
set -e
# Clean up
rm -rf /var/lib/apt/lists/*
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
apt_get_update()
{
if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update -y
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
}
# 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
if [ $(getent group ssh) ]; then
echo "'ssh' group already exists."
else
echo "adding 'ssh' group, as it does not already exist."
groupadd ssh
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
# 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
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
# Clean up
rm -rf /var/lib/apt/lists/*
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"