diff options
Diffstat (limited to 'features/src/common-utils')
-rw-r--r-- | features/src/common-utils/NOTES.md | 26 | ||||
-rw-r--r-- | features/src/common-utils/README.md | 58 | ||||
-rwxr-xr-x | features/src/common-utils/bin/code | 16 | ||||
-rwxr-xr-x | features/src/common-utils/bin/devcontainer-info | 35 | ||||
-rwxr-xr-x | features/src/common-utils/bin/systemctl | 7 | ||||
-rw-r--r-- | features/src/common-utils/devcontainer-feature.json | 69 | ||||
-rwxr-xr-x | features/src/common-utils/install.sh | 36 | ||||
-rw-r--r-- | features/src/common-utils/main.sh | 573 | ||||
-rw-r--r-- | features/src/common-utils/scripts/bash_theme_snippet.sh | 25 | ||||
-rw-r--r-- | features/src/common-utils/scripts/devcontainers.zsh-theme | 26 | ||||
-rw-r--r-- | features/src/common-utils/scripts/rc_snippet.sh | 26 |
11 files changed, 897 insertions, 0 deletions
diff --git a/features/src/common-utils/NOTES.md b/features/src/common-utils/NOTES.md new file mode 100644 index 0000000..f2c7aa7 --- /dev/null +++ b/features/src/common-utils/NOTES.md @@ -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`. diff --git a/features/src/common-utils/README.md b/features/src/common-utils/README.md new file mode 100644 index 0000000..2582dba --- /dev/null +++ b/features/src/common-utils/README.md @@ -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`._ diff --git a/features/src/common-utils/bin/code b/features/src/common-utils/bin/code new file mode 100755 index 0000000..b0d517f --- /dev/null +++ b/features/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/features/src/common-utils/bin/devcontainer-info b/features/src/common-utils/bin/devcontainer-info new file mode 100755 index 0000000..abbb682 --- /dev/null +++ b/features/src/common-utils/bin/devcontainer-info @@ -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 diff --git a/features/src/common-utils/bin/systemctl b/features/src/common-utils/bin/systemctl new file mode 100755 index 0000000..4ead985 --- /dev/null +++ b/features/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/features/src/common-utils/devcontainer-feature.json b/features/src/common-utils/devcontainer-feature.json new file mode 100644 index 0000000..329f8de --- /dev/null +++ b/features/src/common-utils/devcontainer-feature.json @@ -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)" + } + } +} diff --git a/features/src/common-utils/install.sh b/features/src/common-utils/install.sh new file mode 100755 index 0000000..8f1ece4 --- /dev/null +++ b/features/src/common-utils/install.sh @@ -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 $? diff --git a/features/src/common-utils/main.sh b/features/src/common-utils/main.sh new file mode 100644 index 0000000..26f0a75 --- /dev/null +++ b/features/src/common-utils/main.sh @@ -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!" diff --git a/features/src/common-utils/scripts/bash_theme_snippet.sh b/features/src/common-utils/scripts/bash_theme_snippet.sh new file mode 100644 index 0000000..a028e4b --- /dev/null +++ b/features/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/features/src/common-utils/scripts/devcontainers.zsh-theme b/features/src/common-utils/scripts/devcontainers.zsh-theme new file mode 100644 index 0000000..ff11c91 --- /dev/null +++ b/features/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/features/src/common-utils/scripts/rc_snippet.sh b/features/src/common-utils/scripts/rc_snippet.sh new file mode 100644 index 0000000..4810cd9 --- /dev/null +++ b/features/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 |