#!/bin/bash #------------------------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- # # Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md # # This is file is derived from multiple of the image scripts provided by Microsoft (common-debian.sh and # node-debian.sh). The reasoning for the separate maintenance is to provide support for both Node and Mongo # tools on the arm64 architecture. Mongo's own repository does not provide those tools for arm64 on Debian, # on which Microsoft bases all of their Node devcontainer images. The scripts have thus been adapted to work # with one of the official Ubuntu Docker images. The mentioned MongoDB tools are installed via the Dockerfile # that calls this script. export NVM_DIR="/usr/local/share/nvm" export NODE_VERSION="lts" export NVM_VERSION="0.38.0" USERNAME="node" INSTALL_ZSH=${1:-"true"} MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" updaterc() { echo "Updating /etc/bash.bashrc and /etc/zsh/zshrc..." echo -e "$1" >> /etc/bash.bashrc if [ -f "/etc/zsh/zshrc" ]; then echo -e "$1" >> /etc/zsh/zshrc fi } # Function to run apt-get if needed apt_get_update_if_needed() { if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then echo "Running apt-get update..." apt-get update else echo "Skipping apt-get update." fi } # Checks if packages are installed and installs them if not check_packages() { if ! dpkg -s "$@" > /dev/null 2>&1; then apt_get_update_if_needed apt-get -y install --no-install-recommends "$@" 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) fi } # Ensure apt is in non-interactive to avoid prompts export DEBIAN_FRONTEND=noninteractive # Install dependencies check_packages apt-utils \ apt-transport-https \ curl \ ca-certificates \ tar \ gnupg2 \ git \ openssh-client \ gnupg2 \ iproute2 \ procps \ lsof \ htop \ net-tools \ psmisc \ curl \ wget \ rsync \ ca-certificates \ unzip \ zip \ nano \ vim-tiny \ less \ jq \ lsb-release \ apt-transport-https \ dialog \ libc6 \ libgcc1 \ libkrb5-3 \ libgssapi-krb5-2 \ libicu[0-9][0-9] \ liblttng-ust0 \ libstdc++6 \ zlib1g \ locales \ sudo \ ncdu \ man-db \ strace \ manpages \ manpages-dev \ init-system-helpers echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ && chmod 0440 /etc/sudoers.d/$USERNAME # 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 # Install yarn if type yarn > /dev/null 2>&1; then echo "Yarn already installed." else # Import key safely (new method rather than deprecated apt-key approach) and install curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list apt-get update apt-get -y install --no-install-recommends yarn fi # Adjust node version if required if [ "${NODE_VERSION}" = "none" ]; then export NODE_VERSION= elif [ "${NODE_VERSION}" = "lts" ]; then export NODE_VERSION="lts/*" fi # Install the specified node version if NVM directory already exists, then exit if [ -d "${NVM_DIR}" ]; then echo "NVM already installed." if [ "${NODE_VERSION}" != "" ]; then su ${USERNAME} -c ". $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm clear-cache" fi exit 0 fi # Create nvm group, nvm dir, and set sticky bit if ! cat /etc/group | grep -e "^nvm:" > /dev/null 2>&1; then groupadd -r nvm fi umask 0002 usermod -a -G nvm ${USERNAME} mkdir -p ${NVM_DIR} chown :nvm ${NVM_DIR} chmod g+s ${NVM_DIR} su $USERNAME -c "$(cat << EOF set -e umask 0002 # Do not update profile - we'll do this manually export PROFILE=/dev/null curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh | bash source ${NVM_DIR}/nvm.sh if [ "${NODE_VERSION}" != "" ]; then nvm alias default ${NODE_VERSION} fi nvm clear-cache EOF )" 2>&1 # Update rc files updaterc "$(cat < /usr/local/bin/code #!/bin/sh get_in_path_except_current() { which -a "$1" | grep -A1 "$0" | grep -v "$0" } code="$(get_in_path_except_current code)" if [ -n "$code" ]; then exec "$code" "$@" elif [ "$(command -v code-insiders)" ]; then exec code-insiders "$@" else echo "code or code-insiders is not installed" >&2 exit 127 fi EOF chmod +x /usr/local/bin/code # systemctl shim - tells people to use 'service' if systemd is not running cat << 'EOF' > /usr/local/bin/systemctl #!/bin/sh set -e if [ -d "/run/systemd/system" ]; then exec /bin/systemctl/systemctl "$@" else echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services intead. 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='`\ 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`' 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_prompt_info)%{$fg[white]%}$ %{$reset_color%}' # Git status 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}:${USERNAME} "${user_rc_path}/.bashrc" RC_SNIPPET_ALREADY_ADDED="true" fi # Optionally install and configure zsh and Oh My Zsh! if [ "${INSTALL_ZSH}" = "true" ]; then if ! type zsh > /dev/null 2>&1; then apt_get_update_if_needed apt-get install -y zsh fi if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then echo "${rc_snippet}" >> /etc/zsh/zshrc ZSH_ALREADY_INSTALLED="true" fi # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script. oh_my_install_dir="${user_rc_path}/.oh-my-zsh" if [ ! -d "${oh_my_install_dir}" ]; 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}:${USERNAME} "${user_rc_path}" fi fi fi # Write marker file mkdir -p "$(dirname "${MARKER_FILE}")" echo -e "\ PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" echo "Done!"