diff --git a/src/.common/README.md b/src/anaconda/_lib/README.md similarity index 97% rename from src/.common/README.md rename to src/anaconda/_lib/README.md index e614bc005..4273e8865 100644 --- a/src/.common/README.md +++ b/src/anaconda/_lib/README.md @@ -15,7 +15,7 @@ Determines the appropriate non-root user based on the input username. **Usage:** ```bash # Source the helper script -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the user USERNAME=$(determine_user_from_input "${USERNAME}" "root") @@ -110,7 +110,7 @@ fi ```bash # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/.common/common-setup.sh b/src/anaconda/_lib/common-setup.sh similarity index 100% rename from src/.common/common-setup.sh rename to src/anaconda/_lib/common-setup.sh diff --git a/src/anaconda/install.sh b/src/anaconda/install.sh index 2fc5d411a..dd2e523be 100755 --- a/src/anaconda/install.sh +++ b/src/anaconda/install.sh @@ -83,7 +83,7 @@ chmod +x /etc/profile.d/00-restore-env.sh # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/common-utils/_lib/README.md b/src/common-utils/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/common-utils/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/common-utils/_lib/common-setup.sh b/src/common-utils/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/common-utils/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/common-utils/main.sh b/src/common-utils/main.sh index 0374a9302..42a41bc81 100644 --- a/src/common-utils/main.sh +++ b/src/common-utils/main.sh @@ -401,7 +401,7 @@ esac # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Handle the special "none" case for common-utils before user determination # The "none" case sets USER_UID and USER_GID to 0 diff --git a/src/conda/_lib/README.md b/src/conda/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/conda/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/conda/_lib/common-setup.sh b/src/conda/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/conda/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/conda/install.sh b/src/conda/install.sh index 1e7b49089..4d1cf2237 100644 --- a/src/conda/install.sh +++ b/src/conda/install.sh @@ -29,7 +29,7 @@ chmod +x /etc/profile.d/00-restore-env.sh # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/desktop-lite/_lib/README.md b/src/desktop-lite/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/desktop-lite/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/desktop-lite/_lib/common-setup.sh b/src/desktop-lite/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/desktop-lite/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/desktop-lite/install.sh b/src/desktop-lite/install.sh index f704cd7e2..47ae44857 100755 --- a/src/desktop-lite/install.sh +++ b/src/desktop-lite/install.sh @@ -73,7 +73,7 @@ fi # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/docker-in-docker/_lib/README.md b/src/docker-in-docker/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/docker-in-docker/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/docker-in-docker/_lib/common-setup.sh b/src/docker-in-docker/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/docker-in-docker/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/docker-in-docker/install.sh b/src/docker-in-docker/install.sh index d0197c644..e484e40a2 100755 --- a/src/docker-in-docker/install.sh +++ b/src/docker-in-docker/install.sh @@ -46,7 +46,7 @@ fi # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/docker-outside-of-docker/_lib/README.md b/src/docker-outside-of-docker/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/docker-outside-of-docker/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/docker-outside-of-docker/_lib/common-setup.sh b/src/docker-outside-of-docker/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/docker-outside-of-docker/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/docker-outside-of-docker/install.sh b/src/docker-outside-of-docker/install.sh index a81970d51..1b555b522 100755 --- a/src/docker-outside-of-docker/install.sh +++ b/src/docker-outside-of-docker/install.sh @@ -40,7 +40,7 @@ fi # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/go/_lib/README.md b/src/go/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/go/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/go/_lib/common-setup.sh b/src/go/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/go/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/go/install.sh b/src/go/install.sh index 290a1555b..825bae28e 100755 --- a/src/go/install.sh +++ b/src/go/install.sh @@ -176,7 +176,7 @@ fi # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/hugo/_lib/README.md b/src/hugo/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/hugo/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/hugo/_lib/common-setup.sh b/src/hugo/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/hugo/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/hugo/install.sh b/src/hugo/install.sh index 67de944f8..4c605a473 100755 --- a/src/hugo/install.sh +++ b/src/hugo/install.sh @@ -31,7 +31,7 @@ chmod +x /etc/profile.d/00-restore-env.sh # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/java/_lib/README.md b/src/java/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/java/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/java/_lib/common-setup.sh b/src/java/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/java/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/java/install.sh b/src/java/install.sh index 097996f60..39a2d77c6 100644 --- a/src/java/install.sh +++ b/src/java/install.sh @@ -156,7 +156,7 @@ chmod +x /etc/profile.d/00-restore-env.sh # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/kubectl-helm-minikube/_lib/README.md b/src/kubectl-helm-minikube/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/kubectl-helm-minikube/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/kubectl-helm-minikube/_lib/common-setup.sh b/src/kubectl-helm-minikube/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/kubectl-helm-minikube/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/kubectl-helm-minikube/install.sh b/src/kubectl-helm-minikube/install.sh index 6d7ffae07..ce97193d1 100755 --- a/src/kubectl-helm-minikube/install.sh +++ b/src/kubectl-helm-minikube/install.sh @@ -30,7 +30,7 @@ fi # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/node/_lib/README.md b/src/node/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/node/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/node/_lib/common-setup.sh b/src/node/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/node/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/node/install.sh b/src/node/install.sh index ef33289bd..ac04bbed1 100755 --- a/src/node/install.sh +++ b/src/node/install.sh @@ -243,7 +243,7 @@ fi # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/oryx/_lib/README.md b/src/oryx/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/oryx/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/oryx/_lib/common-setup.sh b/src/oryx/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/oryx/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/oryx/install.sh b/src/oryx/install.sh index 6242b7782..47260748e 100755 --- a/src/oryx/install.sh +++ b/src/oryx/install.sh @@ -27,7 +27,7 @@ chmod +x /etc/profile.d/00-restore-env.sh # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/php/_lib/README.md b/src/php/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/php/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/php/_lib/common-setup.sh b/src/php/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/php/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/php/install.sh b/src/php/install.sh index 4b84bc1b4..a71f2ad3a 100755 --- a/src/php/install.sh +++ b/src/php/install.sh @@ -38,7 +38,7 @@ chmod +x /etc/profile.d/00-restore-env.sh # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # If in automatic mode, determine if a user already exists, if not use root USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/python/_lib/README.md b/src/python/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/python/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/python/_lib/common-setup.sh b/src/python/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/python/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/python/install.sh b/src/python/install.sh index 540a9c3c2..75c18c4eb 100755 --- a/src/python/install.sh +++ b/src/python/install.sh @@ -837,7 +837,7 @@ fi # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/ruby/_lib/README.md b/src/ruby/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/ruby/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/ruby/_lib/common-setup.sh b/src/ruby/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/ruby/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/ruby/install.sh b/src/ruby/install.sh index 1063d3377..124070952 100755 --- a/src/ruby/install.sh +++ b/src/ruby/install.sh @@ -41,7 +41,7 @@ chmod +x /etc/profile.d/00-restore-env.sh # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/rust/_lib/README.md b/src/rust/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/rust/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/rust/_lib/common-setup.sh b/src/rust/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/rust/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/rust/install.sh b/src/rust/install.sh index c46b8f50e..5a6410e6d 100755 --- a/src/rust/install.sh +++ b/src/rust/install.sh @@ -103,7 +103,7 @@ chmod +x /etc/profile.d/00-restore-env.sh # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/src/sshd/_lib/README.md b/src/sshd/_lib/README.md new file mode 100644 index 000000000..4273e8865 --- /dev/null +++ b/src/sshd/_lib/README.md @@ -0,0 +1,122 @@ +# Common Helper Scripts + +This directory contains common helper scripts that can be shared across multiple features to avoid code duplication. + +## common-setup.sh + +A helper script that provides common setup functions used across multiple features. + +### Functions + +#### `determine_user_from_input` + +Determines the appropriate non-root user based on the input username. + +**Usage:** +```bash +# Source the helper script +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Parameters:** +- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username) +- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root") + +**Behavior:** +- **"auto" or "automatic"**: + - First checks if `_REMOTE_USER` environment variable is set and is not "root" + - If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list: + 1. `devcontainer` + 2. `vscode` + 3. `node` + 4. `codespace` + 5. User with UID 1000 (from `/etc/passwd`) + - If no user is found, returns the fallback user (default: "root") + +- **"none"**: Always returns "root" + +- **Specific username**: + - Validates the user exists using `id -u` + - If the user exists, returns that username + - If the user doesn't exist, returns "root" + +**Examples:** + +```bash +# Basic usage with default fallback (root) +USERNAME=$(determine_user_from_input "automatic") + +# With custom fallback +USERNAME=$(determine_user_from_input "automatic" "vscode") + +# Explicit user +USERNAME=$(determine_user_from_input "myuser") + +# None (always returns root) +USERNAME=$(determine_user_from_input "none") +``` + +**Return Value:** +Prints the resolved username to stdout, which can be captured using command substitution. + +## Testing + +Tests for the helper scripts are located in `/test/.common/`. Run the tests with: + +```bash +bash test/.common/test-common-setup.sh +``` + +## Edge Cases + +The helper handles several edge cases: + +1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed. + +2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created. + +3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode. + +4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop. + +## Migration Guide + +To migrate an existing feature to use the common helper: + +### Before: +```bash +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi +``` + +### After: +```bash +# Source common helper functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/_lib/common-setup.sh" + +# Determine the appropriate non-root user +USERNAME=$(determine_user_from_input "${USERNAME}" "root") +``` + +**Note:** For features like `common-utils` that create users and need a different fallback, use: +```bash +USERNAME=$(determine_user_from_input "${USERNAME}" "vscode") +``` diff --git a/src/sshd/_lib/common-setup.sh b/src/sshd/_lib/common-setup.sh new file mode 100644 index 000000000..d2ac866cf --- /dev/null +++ b/src/sshd/_lib/common-setup.sh @@ -0,0 +1,87 @@ +#!/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. +#------------------------------------------------------------------------------------------------------------------------- +# +# Helper script for common feature setup tasks, including user selection logic. +# Maintainer: The Dev Container spec maintainers + +# Determine the appropriate non-root user +# Usage: determine_user_from_input USERNAME [FALLBACK_USER] +# +# This function resolves the USERNAME variable based on the input value: +# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user +# - If USERNAME is "none" or doesn't exist, it will fall back to root +# - Otherwise, it validates the specified USERNAME exists +# +# Arguments: +# USERNAME - The username input (typically from feature configuration) +# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root") +# +# Returns: +# The resolved username is printed to stdout +# +# Examples: +# USERNAME=$(determine_user_from_input "automatic") +# USERNAME=$(determine_user_from_input "vscode") +# USERNAME=$(determine_user_from_input "auto" "vscode") +# +determine_user_from_input() { + local input_username="${1:-automatic}" + local fallback_user="${2:-root}" + local resolved_username="" + + if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then + # Automatic mode: try to detect an existing non-root user + + # First, check if _REMOTE_USER is set and is not root + if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then + # Verify the user exists before using it + if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then + resolved_username="${_REMOTE_USER}" + else + # _REMOTE_USER doesn't exist, fall through to normal detection + resolved_username="" + fi + fi + + # If we didn't resolve via _REMOTE_USER, try to find a non-root user + if [ -z "${resolved_username}" ]; then + # Try to find a non-root user from a list of common usernames + # The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000 + local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')") + + for current_user in "${possible_users[@]}"; do + # Skip empty entries + if [ -z "${current_user}" ]; then + continue + fi + + # Check if user exists + if id -u "${current_user}" > /dev/null 2>&1; then + resolved_username="${current_user}" + break + fi + done + + # If no user found, use the fallback + if [ -z "${resolved_username}" ]; then + resolved_username="${fallback_user}" + fi + fi + elif [ "${input_username}" = "none" ]; then + # Explicit "none" means use root + resolved_username="root" + else + # Specific username provided - validate it exists + if id -u "${input_username}" > /dev/null 2>&1; then + resolved_username="${input_username}" + else + # User doesn't exist, fall back to root + resolved_username="root" + fi + fi + + echo "${resolved_username}" +} diff --git a/src/sshd/install.sh b/src/sshd/install.sh index 2bfbdaa4f..f72e1ef48 100755 --- a/src/sshd/install.sh +++ b/src/sshd/install.sh @@ -27,7 +27,7 @@ fi # Source common helper functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../.common/common-setup.sh" +source "${SCRIPT_DIR}/_lib/common-setup.sh" # Determine the appropriate non-root user USERNAME=$(determine_user_from_input "${USERNAME}" "root") diff --git a/test/.common/test-common-setup.sh b/test/.common/test-common-setup.sh index c8f79a1da..2f662bda9 100755 --- a/test/.common/test-common-setup.sh +++ b/test/.common/test-common-setup.sh @@ -6,9 +6,9 @@ set -e -# Source the helper script +# Source the helper script from anaconda feature (all features have identical copies) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../../src/.common/common-setup.sh" +source "${SCRIPT_DIR}/../../src/anaconda/_lib/common-setup.sh" # Test counters PASSED=0