diff options
27 files changed, 979 insertions, 469 deletions
@@ -9,7 +9,7 @@ </tr></table> 'Features' are self-contained units of installation code and development container configuration. Features are designed -to install atop a wide-range of base container images (**this repo focuses on `debian` based images**). +to install atop a wide-range of base container images. Missing a CLI or language in your otherwise _perfect_ container image? Add the relevant Feature to the `features` property of a [`devcontainer.json`](https://containers.dev/implementors/json_reference/#general-properties). A diff --git a/src/common-utils/NOTES.md b/src/common-utils/NOTES.md index 2c07245..77dfd1d 100644 --- a/src/common-utils/NOTES.md +++ b/src/common-utils/NOTES.md @@ -1,12 +1,21 @@ -## Speeding up the command prompt in large repositories +## OS Support -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 since the required git status command can be slow. To resolve this, you can update a git setting to remove the git portion of the command prompt. +This Feature should work on recent versions of Debian/Ubuntu, RedHat Enterprise Linux, Fedora, RockyLinux, and Alpine Linux. -To disable the prompt for the current folder's repository, enter the following in a terminal or add it to your `postCreateCommand` or dotfiles: +## 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 codespaces-theme.hide-status 1 +git config devcontainers-theme.show-dirty 1 ``` -This setting will survive a rebuild since it is applied to the repository rather than the container. +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`.
\ No newline at end of file diff --git a/src/common-utils/bin/code b/src/common-utils/bin/code new file mode 100755 index 0000000..b0d517f --- /dev/null +++ b/src/common-utils/bin/code @@ -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 diff --git a/src/common-utils/bin/devcontainer-info b/src/common-utils/bin/devcontainer-info new file mode 100755 index 0000000..2c74fea --- /dev/null +++ b/src/common-utils/bin/devcontainer-info @@ -0,0 +1,27 @@ +#!/bin/sh +. /usr/local/etc/vscode-dev-containers/meta.env + +# Minimal output +if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then + echo "${VERSION}" + exit 0 +elif [ "$1" = "release" ]; then + echo "${GIT_REPOSITORY_RELEASE}" + exit 0 +elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then + echo "${CONTENTS_URL}" + exit 0 +fi + +#Full output +echo +echo "Development container image information" +echo +if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi +if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi +if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi +if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi +if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi +if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi +if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi +echo diff --git a/src/common-utils/bin/systemctl b/src/common-utils/bin/systemctl new file mode 100755 index 0000000..4ead985 --- /dev/null +++ b/src/common-utils/bin/systemctl @@ -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 diff --git a/src/common-utils/devcontainer-feature.json b/src/common-utils/devcontainer-feature.json index d2a97d6..05bf067 100644 --- a/src/common-utils/devcontainer-feature.json +++ b/src/common-utils/devcontainer-feature.json @@ -1,7 +1,7 @@ { "id": "common-utils", - "version": "1.2.0", - "name": "Common Debian Utilities", + "version": "2.0.0", + "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": { @@ -28,36 +28,37 @@ "username": { "type": "string", "proposals": [ + "devcontainer", "vscode", "codespace", "none", "automatic" ], "default": "automatic", - "description": "Enter name of non-root user to configure or none to skip" + "description": "Enter name of a non-root user to configure or none to skip" }, - "uid": { + "userUid": { "type": "string", "proposals": [ "1000", "automatic" ], "default": "automatic", - "description": "Enter uid for non-root user" + "description": "Enter UID for non-root user" }, - "gid": { + "userGid": { "type": "string", "proposals": [ "1000", "automatic" ], "default": "automatic", - "description": "Enter gid for non-root user" + "description": "Enter GID for non-root user" }, "nonFreePackages": { "type": "boolean", "default": false, - "description": "Add packages from non-free Debian repository?" + "description": "Add packages from non-free Debian repository? (Debian only)" } } } diff --git a/src/common-utils/install.sh b/src/common-utils/install.sh index eb0e624..d08a2f2 100755 --- a/src/common-utils/install.sh +++ b/src/common-utils/install.sh @@ -1,22 +1,19 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------------------------------------- +#!/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. -#------------------------------------------------------------------------------------------------------------- +# Licensed under the MIT License. See https://github.com/devcontainers/features/blob/main/LICENSE for license information. +#------------------------------------------------------------------------------------------------------------------------- # -# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md -# Maintainer: The VS Code and Codespaces Teams +# Docs: https://github.com/devcontainers/features/tree/main/src/common-utils +# Maintainer: The Dev Container spec maintainers set -e -# Clean up -rm -rf /var/lib/apt/lists/* - INSTALL_ZSH="${INSTALLZSH:-"true"}" CONFIGURE_ZSH_AS_DEFAULT_SHELL="${CONFIGUREZSHASDEFAULTSHELL:-"false"}" INSTALL_OH_MY_ZSH="${INSTALLOHMYZSH:-"true"}" UPGRADE_PACKAGES="${UPGRADEPACKAGES:-"true"}" -USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" +USERNAME="${USERNAME:-"automatic"}" USER_UID="${UID:-"automatic"}" USER_GID="${GID:-"automatic"}" ADD_NON_FREE_PACKAGES="${NONFREEPACKAGES:-"false"}" @@ -28,443 +25,11 @@ if [ "$(id -u)" -ne 0 ]; then exit 1 fi -# Ensure that login shells get the correct path if the user updated the PATH using ENV. -rm -f /etc/profile.d/00-restore-env.sh -echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh -chmod +x /etc/profile.d/00-restore-env.sh - -# If in automatic mode, determine if a user already exists, if not use vscode -if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then - USERNAME="" - POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") - for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do - if id -u ${CURRENT_USER} > /dev/null 2>&1; then - USERNAME=${CURRENT_USER} - break - fi - done - if [ "${USERNAME}" = "" ]; then - USERNAME=vscode - fi -elif [ "${USERNAME}" = "none" ]; then - USERNAME=root - USER_UID=0 - USER_GID=0 -fi - -# Load markers to see which steps have already run -if [ -f "${MARKER_FILE}" ]; then - echo "Marker file found:" - cat "${MARKER_FILE}" - source "${MARKER_FILE}" -fi - -# Ensure apt is in non-interactive to avoid prompts -export DEBIAN_FRONTEND=noninteractive - -apt_get_update() -{ - if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then - echo "Running apt-get update..." - apt-get update -y - fi -} - -# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies -if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then - - package_list="apt-utils \ - openssh-client \ - gnupg2 \ - dirmngr \ - iproute2 \ - procps \ - lsof \ - htop \ - net-tools \ - psmisc \ - curl \ - 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" - - # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian - if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then - # Bring in variables from /etc/os-release like VERSION_CODENAME - . /etc/os-release - sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list - sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list - sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list - sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list - # Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html - sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list - echo "Running apt-get update..." - apt-get update - package_list="${package_list} manpages-posix manpages-posix-dev" - else - apt_get_update - fi - - # Install libssl1.1 if available - if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then - package_list="${package_list} libssl1.1" - fi - - # Install libssl3 if available - if [[ ! -z $(apt-cache --names-only search ^libssl3$) ]]; then - package_list="${package_list} libssl3" - fi - - # Install appropriate version of libssl1.0.x if available - libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') - if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then - if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then - # Debian 9 - package_list="${package_list} libssl1.0.2" - elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then - # Ubuntu 18.04, 16.04, earlier - package_list="${package_list} libssl1.0.0" - fi - fi - - echo "Packages to verify are installed: ${package_list}" - apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) - - # Install git if not already installed (may be more recent than distro version) - if ! type git > /dev/null 2>&1; then - apt-get -y install --no-install-recommends git - fi - - PACKAGES_ALREADY_INSTALLED="true" -fi - -# Get to latest versions of all packages -if [ "${UPGRADE_PACKAGES}" = "true" ]; then - apt_get_update - apt-get -y upgrade --no-install-recommends - apt-get autoremove -y -fi - -# Ensure at least the en_US.UTF-8 UTF-8 locale is available. -# Common need for both applications and things like the agnoster ZSH theme. -if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then - echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen - locale-gen - LOCALE_ALREADY_SET="true" -fi - -# Create or update a non-root user to match UID/GID. -group_name="${USERNAME}" -if id -u ${USERNAME} > /dev/null 2>&1; then - # User exists, update if needed - if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then - group_name="$(id -gn $USERNAME)" - groupmod --gid $USER_GID ${group_name} - usermod --gid $USER_GID $USERNAME - fi - if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then - usermod --uid $USER_UID $USERNAME - fi -else - # Create user - if [ "${USER_GID}" = "automatic" ]; then - groupadd $USERNAME - else - groupadd --gid $USER_GID $USERNAME - fi - if [ "${USER_UID}" = "automatic" ]; then - useradd -s /bin/bash --gid $USERNAME -m $USERNAME - else - useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME - fi -fi - -# Add add sudo support for non-root user -if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then - echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME - chmod 0440 /etc/sudoers.d/$USERNAME - EXISTING_NON_ROOT_USER="${USERNAME}" -fi - -# ** Shell customization section ** -if [ "${USERNAME}" = "root" ]; then - user_rc_path="/root" -else - user_rc_path="/home/${USERNAME}" -fi - -# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty -if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then - cp /etc/skel/.bashrc "${user_rc_path}/.bashrc" -fi - -# Restore user .profile defaults from skeleton file if it doesn't exist or is empty -if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then - cp /etc/skel/.profile "${user_rc_path}/.profile" -fi - -# .bashrc/.zshrc snippet -rc_snippet="$(cat << 'EOF' - -if [ -z "${USER}" ]; then export USER=$(whoami); fi -if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi - -# Display optional first run image specific notice if configured and terminal is interactive -if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then - if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then - cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" - elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then - cat "/workspaces/.codespaces/shared/first-run-notice.txt" - fi - mkdir -p "$HOME/.config/vscode-dev-containers" - # Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it - ((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &) -fi - -# Set the default git editor if not already set -if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then - if [ "${TERM_PROGRAM}" = "vscode" ]; then - if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then - export GIT_EDITOR="code-insiders --wait" - else - export GIT_EDITOR="code --wait" - fi - fi +# If we're using Alpine, install bash before executing +. /etc/os-release +if [ "${ID}" = "alpine" ]; then + apk add --no-cache bash fi -EOF -)" - -# code shim, it fallbacks to code-insiders if code is not available -cat << 'EOF' > /usr/local/bin/code -#!/bin/sh - -get_in_path_except_current() { - which -a "$1" | grep -A1 "$0" | grep -v "$0" -} - -code="$(get_in_path_except_current code)" - -if [ -n "$code" ]; then - exec "$code" "$@" -elif [ "$(command -v code-insiders)" ]; then - exec code-insiders "$@" -else - echo "code or code-insiders is not installed" >&2 - exit 127 -fi -EOF -chmod +x /usr/local/bin/code - -# systemctl shim - tells people to use 'service' if systemd is not running -cat << 'EOF' > /usr/local/bin/systemctl -#!/bin/sh -set -e -if [ -d "/run/systemd/system" ]; then - exec /bin/systemctl "$@" -else - echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services instead. e.g.: \n\nservice --status-all' -fi -EOF -chmod +x /usr/local/bin/systemctl - -# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme -codespaces_bash="$(cat \ -<<'EOF' - -# Codespaces bash prompt theme -__bash_prompt() { - local userpart='`export XIT=$? \ - && [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \ - && [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`' - local gitbranch='`\ - if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \ - export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \ - if [ "${BRANCH}" != "" ]; then \ - echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \ - && if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \ - echo -n " \[\033[1;33m\]✗"; \ - fi \ - && echo -n "\[\033[0;36m\]) "; \ - fi; \ - fi`' - local lightblue='\[\033[1;34m\]' - local removecolor='\[\033[0m\]' - PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ " - unset -f __bash_prompt -} -__bash_prompt - -EOF -)" - -codespaces_zsh="$(cat \ -<<'EOF' -# Codespaces zsh prompt theme -__zsh_prompt() { - local prompt_username - if [ ! -z "${GITHUB_USER}" ]; then - prompt_username="@${GITHUB_USER}" - else - prompt_username="%n" - fi - PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow - PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd - PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status - PROMPT+='%{$fg[white]%}$ %{$reset_color%}' - unset -f __zsh_prompt -} -ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}" -ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} " -ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})" -ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})" -__zsh_prompt - -EOF -)" - -# Add RC snippet and custom bash prompt -if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then - echo "${rc_snippet}" >> /etc/bash.bashrc - echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc" - echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc" - if [ "${USERNAME}" != "root" ]; then - echo "${codespaces_bash}" >> "/root/.bashrc" - echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc" - fi - chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc" - RC_SNIPPET_ALREADY_ADDED="true" -fi - -# Optionally install and configure zsh and Oh My Zsh! -if [ "${INSTALL_ZSH}" = "true" ]; then - if ! type zsh > /dev/null 2>&1; then - apt_get_update - apt-get install -y zsh - fi - if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then - echo "${rc_snippet}" >> /etc/zsh/zshrc - ZSH_ALREADY_INSTALLED="true" - fi - - # Change shell of determined user - if [[ "${CONFIGURE_ZSH_AS_DEFAULT_SHELL}" == "true" ]]; then - 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. - oh_my_install_dir="${user_rc_path}/.oh-my-zsh" - if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MY_ZSH}" = "true" ]; then - template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" - user_rc_file="${user_rc_path}/.zshrc" - umask g-w,o-w - mkdir -p ${oh_my_install_dir} - git clone --depth=1 \ - -c core.eol=lf \ - -c core.autocrlf=false \ - -c fsck.zeroPaddedFilemode=ignore \ - -c fetch.fsck.zeroPaddedFilemode=ignore \ - -c receive.fsck.zeroPaddedFilemode=ignore \ - "https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 - echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} - sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file} - - mkdir -p ${oh_my_install_dir}/custom/themes - echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" - # Shrink git while still enabling updates - cd "${oh_my_install_dir}" - git repack -a -d -f --depth=1 --window=1 - # Copy to non-root user if one is specified - if [ "${USERNAME}" != "root" ]; then - cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root - chown -R ${USERNAME}:${group_name} "${user_rc_path}" - fi - fi -fi - -# Persist image metadata info, script if meta.env found in same directory -meta_info_script="$(cat << 'EOF' -#!/bin/sh -. /usr/local/etc/vscode-dev-containers/meta.env - -# Minimal output -if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then - echo "${VERSION}" - exit 0 -elif [ "$1" = "release" ]; then - echo "${GIT_REPOSITORY_RELEASE}" - exit 0 -elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then - echo "${CONTENTS_URL}" - exit 0 -fi - -#Full output -echo -echo "Development container image information" -echo -if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi -if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi -if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi -if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi -if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi -if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi -if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi -echo -EOF -)" -if [ -f "/usr/local/etc/vscode-dev-containers/meta.env" ]; then - echo "${meta_info_script}" > /usr/local/bin/devcontainer-info - chmod +x /usr/local/bin/devcontainer-info -fi - -if [ ! -d "/usr/local/etc/vscode-dev-containers" ]; then - mkdir -p "$(dirname "${MARKER_FILE}")" -fi - -# Write marker file -echo -e "\ - PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ - LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ - EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ - RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ - ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" - -# Clean up -rm -rf /var/lib/apt/lists/* - -echo "Done!" +exec /bin/bash "$(dirname $0)/main.sh" "$@" +exit $? diff --git a/src/common-utils/main.sh b/src/common-utils/main.sh new file mode 100644 index 0000000..63f64ab --- /dev/null +++ b/src/common-utils/main.sh @@ -0,0 +1,496 @@ +#!/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"}" +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="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" + + # 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 + + # 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 + + # 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 + + # Clean up + apt-get -y clean + rm -rf /var/lib/apt/lists/* +} + +# RedHat / RockyLinux / CentOS / Fedora packages +install_redhat_packages() { + local package_list="\ + openssh-clients \ + gnupg2 \ + iproute \ + procps \ + lsof \ + net-tools \ + psmisc \ + curl \ + wget \ + ca-certificates \ + rsync \ + unzip \ + zip \ + nano \ + vim-minimal \ + less \ + jq \ + openssl-libs \ + krb5-libs \ + libicu \ + zlib \ + sudo \ + sed \ + grep \ + which \ + man-db \ + strace" + + # 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 zsh if needed + if [ "${INSTALL_ZSH}" = "true" ] && ! type zsh > /dev/null 2>&1; then + package_list="${package_list} zsh" + fi + + local install_cmd=dnf + if ! type dnf > /dev/null 2>&1; then + install_cmd=yum + fi + ${install_cmd} -y install ${package_list} + + # Get to latest versions of all packages + if [ "${UPGRADE_PACKAGES}" = "true" ]; then + ${install_cmd} upgrade -y + fi +} + +# Alpine Linux packages +install_alpine_packages() { + apk update + 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 + + # Install zsh if needed + if [ "${INSTALL_ZSH}" = "true" ] && ! type zsh > /dev/null 2>&1; then + apk add --no-cache zsh + fi +} + +# ****************** +# ** 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 independant of distro variants +if [ "${ID}" = "debian" ] || [ "${ID_LIKE}" = "debian" ]; then + ADJUSTED_ID="debian" +elif [[ "${ID}" = "rhel" || "${ID}" = "fedora" || "${ID_LIKE}" = *"rhel"* || "${ID_LIKE}" = *"fedora"* ]]; 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 +if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then + case "${ADJUSTED_ID}" in + "debian") + install_debian_packages + ;; + "rhel") + install_redhat_packages + ;; + "alpine") + install_alpine_packages + ;; + esac + PACKAGES_ALREADY_INSTALLED="true" +fi + +# 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_rc_path="/root" +else + user_rc_path="/home/${USERNAME}" +fi + +# Restore user .bashrc / .profile / .zshrc defaults from skeleton file if it doesn't exist or is empty +possible_rc_files=( ".bashrc" ".profile" ".zshrc" ) +for rc_file in "${possible_rc_files[@]}"; do + if [ -f "/etc/skel/${rc_file}" ]; then + if [ ! -e "${user_rc_path}/${rc_file}" ] || [ -s "${user_rc_path}/${rc_file}" ]; then + cp "/etc/skel/${rc_file}" "${user_rc_path}/${rc_file}" + chown ${USERNAME}:${group_name} "${user_rc_path}/${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" + ;; + esac + cat "${FEATURE_DIR}/scripts/rc_snippet.sh" >> ${global_rc_path} + cat "${FEATURE_DIR}/scripts/bash_theme_snippet.sh" >> "${user_rc_path}/.bashrc" + if [ "${USERNAME}" != "root" ]; then + cat "${FEATURE_DIR}/scripts/bash_theme_snippet.sh" >> "/root/.bashrc" + chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc" + fi + RC_SNIPPET_ALREADY_ADDED="true" +fi + +# Optionally configure zsh and Oh My Zsh! +if [ "${INSTALL_ZSH}" = "true" ]; then + 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" >> /etc/zshrc + ZSH_ALREADY_INSTALLED="true" + fi + + if [ "${CONFIGURE_ZSH_AS_DEFAULT_SHELL}" == "true" ]; then + 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. + oh_my_install_dir="${user_rc_path}/.oh-my-zsh" + if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MY_ZSH}" = "true" ]; then + template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" + user_rc_file="${user_rc_path}/.zshrc" + umask g-w,o-w + mkdir -p ${oh_my_install_dir} + git clone --depth=1 \ + -c core.eol=lf \ + -c core.autocrlf=false \ + -c fsck.zeroPaddedFilemode=ignore \ + -c fetch.fsck.zeroPaddedFilemode=ignore \ + -c receive.fsck.zeroPaddedFilemode=ignore \ + "https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 + echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} + sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="devcontainers"/g' ${user_rc_file} + + # 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 -s "${oh_my_install_dir}/custom/themes/devcontainers.zsh-theme" "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" + + # Shrink git while still enabling updates + cd "${oh_my_install_dir}" + git repack -a -d -f --depth=1 --window=1 + # Copy to non-root user if one is specified + if [ "${USERNAME}" != "root" ]; then + cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root + chown -R ${USERNAME}:${group_name} "${oh_my_install_dir}" "${user_rc_file}" + fi + fi +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 +x /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 +x /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" ]; then + cp -f "${FEATURE_DIR}/bin/devcontainer-info" /usr/local/bin/devcontainer-info + chmod +x /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!" diff --git a/src/common-utils/scripts/bash_theme_snippet.sh b/src/common-utils/scripts/bash_theme_snippet.sh new file mode 100644 index 0000000..a028e4b --- /dev/null +++ b/src/common-utils/scripts/bash_theme_snippet.sh @@ -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 diff --git a/src/common-utils/scripts/devcontainers.zsh-theme b/src/common-utils/scripts/devcontainers.zsh-theme new file mode 100644 index 0000000..ff11c91 --- /dev/null +++ b/src/common-utils/scripts/devcontainers.zsh-theme @@ -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 diff --git a/src/common-utils/scripts/rc_snippet.sh b/src/common-utils/scripts/rc_snippet.sh new file mode 100644 index 0000000..3aa319a --- /dev/null +++ b/src/common-utils/scripts/rc_snippet.sh @@ -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
\ No newline at end of file diff --git a/test/common-utils/alpine.sh b/test/common-utils/alpine.sh new file mode 100755 index 0000000..c5ff866 --- /dev/null +++ b/test/common-utils/alpine.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${ID}" = "alpine" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/already-run.sh b/test/common-utils/already-run.sh new file mode 100755 index 0000000..39d6f9e --- /dev/null +++ b/test/common-utils/already-run.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "non-root user" test "$(whoami)" = "vscode" +check "jq" jq --version +check "curl" curl --version +check "git" git --version +check "zsh" zsh --version +check "ps" ps --version +check "Oh My Zsh! theme" test -e $HOME/.oh-my-zsh/custom/themes + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/alternate-values.sh b/test/common-utils/alternate-values.sh new file mode 100755 index 0000000..d208a27 --- /dev/null +++ b/test/common-utils/alternate-values.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +check "non-root user" id alternate +check "running as root" test "$(whoami)" = "root" +check "jq" jq --version +check "curl" curl --version +check "git" git --version +check "ps" ps --version +check "no zsh" bash -c '! zsh --version' +check "No Oh My Zsh!" test ! -e /home/alternate/.oh-my-zsh/custom/themes/devcontainers.zsh-theme + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/bionic.sh b/test/common-utils/bionic.sh new file mode 100755 index 0000000..349b4c3 --- /dev/null +++ b/test/common-utils/bionic.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${VERSION_CODENAME}" = "bionic" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/bullseye.sh b/test/common-utils/bullseye.sh new file mode 100755 index 0000000..5a396a7 --- /dev/null +++ b/test/common-utils/bullseye.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${VERSION_CODENAME}" = "bullseye" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/buster.sh b/test/common-utils/buster.sh new file mode 100755 index 0000000..9fd2f40 --- /dev/null +++ b/test/common-utils/buster.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${VERSION_CODENAME}" = "buster" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/centos-7.sh b/test/common-utils/centos-7.sh new file mode 100755 index 0000000..a81ae2f --- /dev/null +++ b/test/common-utils/centos-7.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${VERSION_ID}" = "7" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/fedora.sh b/test/common-utils/fedora.sh new file mode 100755 index 0000000..b32a7f1 --- /dev/null +++ b/test/common-utils/fedora.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${ID}" = "fedora" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/focal.sh b/test/common-utils/focal.sh new file mode 100755 index 0000000..28d8b8b --- /dev/null +++ b/test/common-utils/focal.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${VERSION_CODENAME}" = "focal" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/jammy.sh b/test/common-utils/jammy.sh new file mode 100755 index 0000000..f11cac6 --- /dev/null +++ b/test/common-utils/jammy.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${VERSION_CODENAME}" = "jammy" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/rocky-8.sh b/test/common-utils/rocky-8.sh new file mode 100755 index 0000000..f6606db --- /dev/null +++ b/test/common-utils/rocky-8.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${PLATFORM_ID}" = "platform:el8" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/rocky-9.sh b/test/common-utils/rocky-9.sh new file mode 100755 index 0000000..c4b5244 --- /dev/null +++ b/test/common-utils/rocky-9.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +check "non-root user" test "$(whoami)" = "devcontainer" +check "distro" test "${PLATFORM_ID}" = "platform:el9" + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/scenarios.json b/test/common-utils/scenarios.json index aa36fcb..9b0bc85 100644 --- a/test/common-utils/scenarios.json +++ b/test/common-utils/scenarios.json @@ -1,10 +1,111 @@ { - "configure_zsh_as_default_shell": { - "image": "mcr.microsoft.com/devcontainers/base:ubuntu", - "features": { - "common-utils": { - "configureZshAsDefaultShell": true - } + "bionic": { + "image": "ubuntu:bionic", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "focal": { + "image": "ubuntu:focal", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "jammy": { + "image": "ubuntu:jammy", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "buster": { + "image": "debian:buster", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "bullseye": { + "image": "debian:bullseye", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "centos-7": { + "image": "centos:7", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "rocky-8": { + "image": "rockylinux:8", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "rocky-9": { + "image": "rockylinux:9", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "fedora": { + "image": "fedora", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "alpine": { + "image": "alpine", + "remoteUser": "devcontainer", + "features": { + "common-utils": {} + } + }, + "alternate-values": { + "image": "debian:bullseye", + "features": { + "common-utils": { + "username": "alternate", + "userUid": "1001", + "userGid": "1002", + "upgradePackages": false, + "installZsh": false, + "nonFreePackages": true + } + } + }, + "username-default": { + "image": "debian:bullseye", + "features": { + "common-utils": {} + } + }, + "username-detected": { + "image": "node", + "features": { + "common-utils": {} + } + }, + "already-run": { + "image": "mcr.microsoft.com/devcontainers/base:jammy", + "features": { + "common-utils": {} + } + }, + "configure_zsh_as_default_shell": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "common-utils": { + "configureZshAsDefaultShell": true + } + } } - } } diff --git a/test/common-utils/test.sh b/test/common-utils/test.sh index 1282015..5e16a33 100755 --- a/test/common-utils/test.sh +++ b/test/common-utils/test.sh @@ -8,6 +8,11 @@ source dev-container-features-test-lib # Definition specific tests check "jq" jq --version check "curl" curl --version +check "git" git --version +check "zsh" zsh --version +check "ps" ps --version +check "Oh My Zsh! theme" test -e $HOME/.oh-my-zsh/custom/themes/devcontainers.zsh-theme +check "zsh theme symlink" test -e $HOME/.oh-my-zsh/custom/themes/codespaces.zsh-theme # Report result reportResults
\ No newline at end of file diff --git a/test/common-utils/username-default.sh b/test/common-utils/username-default.sh new file mode 100755 index 0000000..49784b7 --- /dev/null +++ b/test/common-utils/username-default.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +# .oh-my-zsh folder would only exist if user defaulting worked +check "non-root user" ls /home/vscode/.oh-my-zsh + + +# Report result +reportResults
\ No newline at end of file diff --git a/test/common-utils/username-detected.sh b/test/common-utils/username-detected.sh new file mode 100755 index 0000000..39c1b29 --- /dev/null +++ b/test/common-utils/username-detected.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +# Definition specific tests +. /etc/os-release +# .oh-my-zsh folder would only exist if user detection worked +check "non-root user" ls /home/node/.oh-my-zsh + +# Report result +reportResults
\ No newline at end of file |