diff --git a/.github/workflows/build-documentation.yml b/.github/workflows/build-documentation.yml index b6d5a8b..0410b72 100644 --- a/.github/workflows/build-documentation.yml +++ b/.github/workflows/build-documentation.yml @@ -13,13 +13,21 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 - - name: Run Sphinx documentation build - uses: ammaraskar/sphinx-action@0.4 + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 with: - docs-folder: "docs/" + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r docs/requirements.txt + - name: Build documentation with Sphinx + run: | + cd docs + sphinx-build -b html . _build/html - name: Upload documentation artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: documentation path: docs/_build/html/ diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 95e9e28..2d41469 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -8,6 +8,7 @@ on: paths: - docs/** - .github/workflows/build-documentation.yml + - .github/workflows/pages.yml - CHANGELOG.md # Allows you to run this workflow manually from the Actions tab @@ -33,18 +34,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Pages - uses: actions/configure-pages@v3 - - name: Run Sphinx documentation build - uses: ammaraskar/sphinx-action@0.4 + uses: actions/configure-pages@v5 + - name: Set up Python + uses: actions/setup-python@v5 with: - docs-folder: "docs/" + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r docs/requirements.txt + - name: Build documentation with Sphinx + run: | + cd docs + sphinx-build -b html . _build/html - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: - # Upload entire repository path: 'docs/_build/html/' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/push-release-to-brew.yml b/.github/workflows/push-release-to-brew.yml index 8c0c64a..e4529a3 100644 --- a/.github/workflows/push-release-to-brew.yml +++ b/.github/workflows/push-release-to-brew.yml @@ -6,7 +6,7 @@ jobs: update-homebrew: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.TOKEN }} repository: dockergiant/homebrew-roll diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index c73c148..04fbfd7 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -10,7 +10,7 @@ jobs: create-tag: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Update Files env: VERSION: ${{ inputs.version }} diff --git a/bin/roll b/bin/roll index f544cb2..ad6162c 100755 --- a/bin/roll +++ b/bin/roll @@ -13,6 +13,8 @@ readonly ROLL_DIR="$( && pwd )" source "${ROLL_DIR}/utils/core.sh" +source "${ROLL_DIR}/utils/config.sh" +source "${ROLL_DIR}/utils/registry.sh" source "${ROLL_DIR}/utils/env.sh" ## verify docker is installed @@ -46,37 +48,15 @@ if (( "$#" )); then ROLL_ENV_PATH="$(locateEnvPath 2>/dev/null)" || true ROLL_ENV_TYPE="$(renderEnvType 2>/dev/null)" || true - if [[ -f "${ROLL_HOME_DIR}/reclu/${1}.cmd" ]]; then + # Use registry system for command discovery + COMMAND_RESULT="$(findCommand "$1")" + + if [[ "$COMMAND_RESULT" =~ ^found: ]]; then ROLL_CMD_VERB="$1" - ROLL_CMD_EXEC="${ROLL_HOME_DIR}/reclu/${1}.cmd" - ROLL_CMD_HELP="${ROLL_HOME_DIR}/reclu/${1}.help" - shift - elif [[ -f "${ROLL_HOME_DIR}/reclu/${ROLL_ENV_TYPE}/${1}.cmd" ]]; then - ROLL_CMD_VERB="$1" - ROLL_CMD_EXEC="${ROLL_HOME_DIR}/reclu/${ROLL_ENV_TYPE}/${1}.cmd" - ROLL_CMD_HELP="${ROLL_HOME_DIR}/reclu/${ROLL_ENV_TYPE}/${1}.help" - shift - elif [[ -f "${ROLL_DIR}/commands/${1}.cmd" ]]; then - ROLL_CMD_VERB="$1" - ROLL_CMD_EXEC="${ROLL_DIR}/commands/${1}.cmd" - ROLL_CMD_HELP="${ROLL_DIR}/commands/${1}.help" - shift - elif [[ -f "${ROLL_DIR}/commands/${ROLL_ENV_TYPE}/${1}.cmd" ]]; then - ROLL_CMD_VERB="$1" - ROLL_CMD_EXEC="${ROLL_DIR}/commands/${ROLL_ENV_TYPE}/${1}.cmd" - ROLL_CMD_HELP="${ROLL_DIR}/commands/${ROLL_ENV_TYPE}/${1}.help" - shift - elif [[ -f "${ROLL_ENV_PATH}/.roll/commands/${1}.cmd" ]]; then - ROLL_CMD_VERB="$1" - ROLL_CMD_ANYARGS+=("$1") - ROLL_CMD_EXEC="${ROLL_ENV_PATH}/.roll/commands/${1}.cmd" - ROLL_CMD_HELP="${ROLL_ENV_PATH}/.roll/commands/${1}.help" - shift - elif [[ -f "${ROLL_HOME_DIR}/commands/${1}.cmd" ]]; then - ROLL_CMD_VERB="$1" - ROLL_CMD_ANYARGS+=("$1") - ROLL_CMD_EXEC="${ROLL_HOME_DIR}/commands/${1}.cmd" - ROLL_CMD_HELP="${ROLL_HOME_DIR}/commands/${1}.help" + # Extract cmd_path and help_path from "found:cmd_path:help_path" + COMMAND_RESULT="${COMMAND_RESULT#found:}" + ROLL_CMD_EXEC="${COMMAND_RESULT%%:*}" + ROLL_CMD_HELP="${COMMAND_RESULT#*:}" shift else ROLL_HELP=1 diff --git a/commands/config.cmd b/commands/config.cmd new file mode 100644 index 0000000..d0b1494 --- /dev/null +++ b/commands/config.cmd @@ -0,0 +1,238 @@ +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +if (( ${#ROLL_PARAMS[@]} == 0 )) || [[ "${ROLL_PARAMS[0]}" == "help" ]]; then + roll config --help || exit $? && exit $? +fi + +## Sub-command execution +case "${ROLL_PARAMS[0]}" in + show) + # Try to load configuration if in a project directory + if ROLL_ENV_PATH="$(locateEnvPath 2>/dev/null)"; then + loadRollConfig "${ROLL_ENV_PATH}" >/dev/null 2>&1 || { + error "Failed to load configuration from ${ROLL_ENV_PATH}/.env.roll" + exit 1 + } + else + warning "Not in a Roll project directory" + exit 1 + fi + + # Filter configuration if specified + filter="${ROLL_PARAMS[1]:-}" + showConfig "$filter" + ;; + + validate) + config_file="${ROLL_PARAMS[1]:-}" + + if [[ -z "$config_file" ]]; then + if ROLL_ENV_PATH="$(locateEnvPath 2>/dev/null)"; then + config_file="${ROLL_ENV_PATH}/.env.roll" + else + error "No configuration file specified and not in a Roll project directory" + exit 1 + fi + fi + + if [[ ! -f "$config_file" ]]; then + error "Configuration file not found: $config_file" + exit 1 + fi + + info "Validating configuration: $config_file" + + if validateConfig "$config_file"; then + success "Configuration is valid" + + # Also check for conflicts if we can load the config + if loadRollConfig "$(dirname "$config_file")" >/dev/null 2>&1; then + if checkConfigConflicts >/dev/null 2>&1; then + success "No configuration conflicts detected" + else + warning "Configuration conflicts detected (see above)" + exit 1 + fi + fi + else + error "Configuration validation failed" + exit 1 + fi + ;; + + conflicts) + # Check for configuration conflicts + if ROLL_ENV_PATH="$(locateEnvPath 2>/dev/null)"; then + loadRollConfig "${ROLL_ENV_PATH}" >/dev/null 2>&1 || { + error "Failed to load configuration from ${ROLL_ENV_PATH}/.env.roll" + exit 1 + } + else + error "Not in a Roll project directory" + exit 1 + fi + + info "Checking for configuration conflicts..." + + if checkConfigConflicts; then + success "No configuration conflicts detected" + else + error "Configuration conflicts detected (see above)" + exit 1 + fi + ;; + + schema) + # Display configuration schema + initConfigSchema + + echo -e "\033[33mRoll Configuration Schema:\033[0m" + echo "" + + # Group configurations by category + echo -e "\033[36mCore Configuration:\033[0m" + i=0 + while [[ $i -lt ${#ROLL_CONFIG_SCHEMA_KEYS[@]} ]]; do + key="${ROLL_CONFIG_SCHEMA_KEYS[$i]}" + value="${ROLL_CONFIG_SCHEMA_VALUES[$i]}" + case "$key" in + ROLL_ENV_NAME|ROLL_ENV_TYPE|ROLL_ENV_SUBT) + printf " %-30s %s\n" "$key" "$value" + ;; + esac + i=$((i + 1)) + done + + echo "" + echo -e "\033[36mService Toggles:\033[0m" + i=0 + while [[ $i -lt ${#ROLL_CONFIG_SCHEMA_KEYS[@]} ]]; do + key="${ROLL_CONFIG_SCHEMA_KEYS[$i]}" + value="${ROLL_CONFIG_SCHEMA_VALUES[$i]}" + if [[ "$key" =~ ^ROLL_(NGINX|DB|REDIS|DRAGONFLY|VARNISH|ELASTICSEARCH|OPENSEARCH|ELASTICVUE|RABBITMQ|MONGODB|BROWSERSYNC|SELENIUM|TEST_DB|ALLURE|MAGEPACK|INCLUDE_GIT) ]] && [[ ! "$key" =~ _VERSION$ ]]; then + printf " %-30s %s\n" "$key" "$value" + fi + i=$((i + 1)) + done + + echo "" + echo -e "\033[36mPHP/Node/Composer Configuration:\033[0m" + i=0 + while [[ $i -lt ${#ROLL_CONFIG_SCHEMA_KEYS[@]} ]]; do + key="${ROLL_CONFIG_SCHEMA_KEYS[$i]}" + value="${ROLL_CONFIG_SCHEMA_VALUES[$i]}" + if [[ "$key" =~ ^(PHP_|COMPOSER_|NODE_) ]] || [[ "$key" =~ ^XDEBUG ]]; then + printf " %-30s %s\n" "$key" "$value" + fi + i=$((i + 1)) + done + + echo "" + echo -e "\033[36mDatabase Configuration:\033[0m" + i=0 + while [[ $i -lt ${#ROLL_CONFIG_SCHEMA_KEYS[@]} ]]; do + key="${ROLL_CONFIG_SCHEMA_KEYS[$i]}" + value="${ROLL_CONFIG_SCHEMA_VALUES[$i]}" + if [[ "$key" =~ ^(DB_|MYSQL_|MARIADB_) ]]; then + printf " %-30s %s\n" "$key" "$value" + fi + i=$((i + 1)) + done + + echo "" + echo -e "\033[36mService Version Configuration:\033[0m" + i=0 + while [[ $i -lt ${#ROLL_CONFIG_SCHEMA_KEYS[@]} ]]; do + key="${ROLL_CONFIG_SCHEMA_KEYS[$i]}" + value="${ROLL_CONFIG_SCHEMA_VALUES[$i]}" + if [[ "$key" =~ _VERSION$ ]] && [[ ! "$key" =~ ^(PHP_|DB_|MYSQL_|MARIADB_|NODE_|XDEBUG_|COMPOSER_) ]]; then + printf " %-30s %s\n" "$key" "$value" + fi + i=$((i + 1)) + done + + echo "" + echo -e "\033[36mTraefik/Network Configuration:\033[0m" + i=0 + while [[ $i -lt ${#ROLL_CONFIG_SCHEMA_KEYS[@]} ]]; do + key="${ROLL_CONFIG_SCHEMA_KEYS[$i]}" + value="${ROLL_CONFIG_SCHEMA_VALUES[$i]}" + if [[ "$key" =~ ^TRAEFIK_ ]]; then + printf " %-30s %s\n" "$key" "$value" + fi + i=$((i + 1)) + done + ;; + + set) + if [[ ${#ROLL_PARAMS[@]} -lt 3 ]]; then + error "Usage: roll config set " + exit 1 + fi + + key="${ROLL_PARAMS[1]}" + value="${ROLL_PARAMS[2]}" + + # Validate the configuration value + initConfigSchema + if ! validateConfigValue "$key" "$value"; then + error "Invalid value for $key: $value" + exit 1 + fi + + # Find configuration file + if ROLL_ENV_PATH="$(locateEnvPath 2>/dev/null)"; then + config_file="${ROLL_ENV_PATH}/.env.roll" + else + error "Not in a Roll project directory" + exit 1 + fi + + # Create backup + cp "$config_file" "${config_file}.backup.$(date +%Y%m%d_%H%M%S)" + + # Update or add the configuration + if grep -q "^${key}=" "$config_file"; then + # Update existing key - use different approach for macOS compatibility + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s/^${key}=.*/${key}=${value}/" "$config_file" + else + sed -i "s/^${key}=.*/${key}=${value}/" "$config_file" + fi + else + # Add new key + echo "${key}=${value}" >> "$config_file" + fi + + success "Configuration updated: ${key}=${value}" + info "Backup created: ${config_file}.backup.$(date +%Y%m%d_%H%M%S)" + ;; + + get) + if [[ ${#ROLL_PARAMS[@]} -lt 2 ]]; then + error "Usage: roll config get [default]" + exit 1 + fi + + key="${ROLL_PARAMS[1]}" + default_value="${ROLL_PARAMS[2]:-}" + + # Load configuration if in project directory + if ROLL_ENV_PATH="$(locateEnvPath 2>/dev/null)"; then + loadRollConfig "${ROLL_ENV_PATH}" >/dev/null 2>&1 || { + error "Failed to load configuration" + exit 1 + } + fi + + value="$(getConfig "$key" "$default_value")" + echo "$value" + ;; + + *) + error "Unknown config command: ${ROLL_PARAMS[0]}" + echo "Available commands: show, validate, conflicts, schema, set, get" + exit 1 + ;; +esac \ No newline at end of file diff --git a/commands/config.help b/commands/config.help new file mode 100644 index 0000000..b5e938a --- /dev/null +++ b/commands/config.help @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +ROLL_USAGE=$(cat < [options] + +\033[33mCommands:\033[0m + show [filter] Display current environment configuration + Optional filter to show only matching keys (regex) + + validate [file] Validate configuration file syntax and values + Uses current environment config if no file specified + + conflicts Check for configuration conflicts and compatibility issues + + schema Display the configuration schema with all available options + + set Set configuration value in current environment + Creates backup before modifying + + get [default] Get configuration value from current environment + Returns default if key is not set + +\033[33mExamples:\033[0m + roll config show # Show all configuration + roll config show ROLL_ # Show only ROLL_* variables + roll config validate # Validate current environment config + roll config conflicts # Check for conflicts + roll config schema # Show configuration schema + roll config set ROLL_REDIS 1 # Enable Redis + roll config get PHP_VERSION 8.1 # Get PHP version (default 8.1) + +\033[33mOptions:\033[0m + -h, --help Display this help menu + +\033[33mNotes:\033[0m + • Configuration files use KEY=value format + • Boolean values must be 0 or 1 + • The 'set' command creates automatic backups + • Use 'schema' to see all available configuration options +EOF +) \ No newline at end of file diff --git a/commands/install.help b/commands/install.help new file mode 100644 index 0000000..8800f12 --- /dev/null +++ b/commands/install.help @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +ROLL_USAGE=$(cat <&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +if (( ${#ROLL_PARAMS[@]} == 0 )) || [[ "${ROLL_PARAMS[0]}" == "help" ]]; then + roll registry --help || exit $? && exit $? +fi + +## Sub-command execution +case "${ROLL_PARAMS[0]}" in + list) + # List all available commands + filter="${ROLL_PARAMS[1]:-}" + category="${ROLL_PARAMS[2]:-}" + + initializeRegistry + + if [[ -n "$category" ]]; then + echo -e "\033[33mCommands in '${category}' category:\033[0m" + listRegisteredCommands "$filter" "$category" + elif [[ -n "$filter" ]]; then + echo -e "\033[33mCommands matching '${filter}':\033[0m" + listRegisteredCommands "$filter" + else + echo -e "\033[33mAll registered commands:\033[0m" + listRegisteredCommands + fi + ;; + + categories) + # List commands organized by category + category="${ROLL_PARAMS[1]:-}" + + initializeRegistry + + if [[ -n "$category" ]]; then + echo -e "\033[33m${category^} Commands:\033[0m" + listCommandsByCategory "$category" + else + listCommandsByCategory + fi + ;; + + info) + # Show detailed information about a specific command + if [[ ${#ROLL_PARAMS[@]} -lt 2 ]]; then + error "Usage: roll registry info " + exit 1 + fi + + command="${ROLL_PARAMS[1]}" + + initializeRegistry + + if ! isCommandRegistered "$command"; then + error "Command '$command' not found in registry" + exit 1 + fi + + echo -e "\033[33mCommand Information: $command\033[0m" + echo " Path: $(getCommandInfo "$command" "path")" + echo " Help File: $(getCommandInfo "$command" "help")" + echo " Category: $(getCommandInfo "$command" "category")" + echo " Priority: $(getCommandInfo "$command" "priority")" + echo " Description: $(getCommandInfo "$command" "description")" + ;; + + search) + # Search for commands by name or description + if [[ ${#ROLL_PARAMS[@]} -lt 2 ]]; then + error "Usage: roll registry search " + exit 1 + fi + + pattern="${ROLL_PARAMS[1]}" + + initializeRegistry + + echo -e "\033[33mSearching for commands matching: '$pattern'\033[0m" + echo "" + + found=0 + i=0 + while [[ $i -lt ${#ROLL_REGISTRY_COMMANDS[@]} ]]; do + command="${ROLL_REGISTRY_COMMANDS[$i]}" + description="${ROLL_REGISTRY_DESCRIPTIONS[$i]}" + category="${ROLL_REGISTRY_CATEGORIES[$i]}" + + if [[ "$command" =~ $pattern ]] || [[ "$description" =~ $pattern ]]; then + printf " %-20s %-10s %s\n" "$command" "[$category]" "$description" + found=1 + fi + i=$((i + 1)) + done + + if [[ $found -eq 0 ]]; then + info "No commands found matching '$pattern'" + fi + ;; + + stats) + # Display registry statistics + showRegistryStats + ;; + + refresh) + # Refresh the command registry + info "Refreshing command registry..." + refreshRegistry + success "Command registry refreshed" + showRegistryStats + ;; + + export) + # Export command list in various formats + format="${ROLL_PARAMS[1]:-simple}" + + case "$format" in + json|csv|simple) + exportCommands "$format" + ;; + *) + error "Unsupported export format: $format" + echo "Supported formats: simple, json, csv" + exit 1 + ;; + esac + ;; + + validate) + # Validate registry integrity + initializeRegistry + + info "Validating command registry integrity..." + + errors=0 + i=0 + while [[ $i -lt ${#ROLL_REGISTRY_COMMANDS[@]} ]]; do + command="${ROLL_REGISTRY_COMMANDS[$i]}" + cmd_path="${ROLL_REGISTRY_PATHS[$i]}" + help_path="${ROLL_REGISTRY_HELP_PATHS[$i]}" + + # Check if command file exists + if [[ ! -f "$cmd_path" ]]; then + error "Command file missing: $cmd_path (for command: $command)" + errors=$((errors + 1)) + fi + + # Check if help file exists (warning only) + if [[ ! -f "$help_path" ]]; then + warning "Help file missing: $help_path (for command: $command)" + fi + + i=$((i + 1)) + done + + if [[ $errors -eq 0 ]]; then + success "Registry validation passed" + else + error "Registry validation failed with $errors errors" + exit 1 + fi + ;; + + paths) + # Show command search paths and their priorities + echo -e "\033[33mCommand Search Paths (by priority):\033[0m" + echo "" + + echo -e "\033[36mGlobal Command Paths:\033[0m" + for search_path in "${ROLL_COMMAND_SEARCH_PATHS[@]}"; do + priority="${search_path%%:*}" + directory="${search_path##*:}" + status="❌" + [[ -d "$directory" ]] && status="✅" + + printf " %s Priority %s: %s\n" "$status" "$priority" "$directory" + done + + if [[ -n "${ROLL_ENV_TYPE}" ]]; then + echo "" + echo -e "\033[36mEnvironment-Specific Paths (${ROLL_ENV_TYPE}):\033[0m" + while IFS= read -r env_path; do + [[ -z "$env_path" ]] && continue + priority="${env_path%%:*}" + directory="${env_path##*:}" + status="❌" + [[ -d "$directory" ]] && status="✅" + + printf " %s Priority %s: %s\n" "$status" "$priority" "$directory" + done < <(getEnvCommandPaths) + else + echo "" + info "No environment loaded - environment-specific paths not shown" + fi + ;; + + *) + error "Unknown registry command: ${ROLL_PARAMS[0]}" + echo "Available commands: list, categories, info, search, stats, refresh, export, validate, paths" + exit 1 + ;; +esac \ No newline at end of file diff --git a/commands/registry.help b/commands/registry.help new file mode 100644 index 0000000..c3f7702 --- /dev/null +++ b/commands/registry.help @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +echo -e "\033[33mUsage:\033[0m" +echo " registry [options]" +echo "" +echo -e "\033[33mCommands:\033[0m" +echo " list [filter] [category] List all registered commands" +echo " Optional filter to match command names (regex)" +echo " Optional category to filter by category" +echo "" +echo " categories [category] List commands organized by category" +echo " Show specific category if provided" +echo "" +echo " info Show detailed information about a specific command" +echo " Including path, help file, category, and priority" +echo "" +echo " search Search for commands by name or description" +echo " Pattern can be regex for flexible matching" +echo "" +echo " stats Display registry statistics and category counts" +echo "" +echo " refresh Refresh the command registry by rescanning directories" +echo " Useful after adding new commands" +echo "" +echo " export [format] Export command list in specified format" +echo " Formats: simple (default), json, csv" +echo "" +echo " validate Validate registry integrity" +echo " Check if command and help files exist" +echo "" +echo " paths Show command search paths and their priorities" +echo " Displays which directories are scanned" +echo "" +echo -e "\033[33mExamples:\033[0m" +echo " roll registry list # List all commands" +echo " roll registry list config # List commands matching 'config'" +echo " roll registry list \"\" magento2 # List commands in magento2 category" +echo " roll registry categories # Show all categories with commands" +echo " roll registry info config # Show details about config command" +echo " roll registry search database # Search for database-related commands" +echo " roll registry export json # Export commands as JSON" +echo " roll registry validate # Check registry integrity" +echo "" +echo -e "\033[33mOptions:\033[0m" +echo " -h, --help Display this help menu" +echo "" +echo -e "\033[33mNotes:\033[0m" +echo " - Commands are discovered from multiple directories with priorities" +echo " - Lower priority numbers have higher precedence" +echo " - Environment-specific commands override global commands" +echo " - The registry caches command information for performance" +echo " - Use 'refresh' if you add new commands and they don't appear" \ No newline at end of file diff --git a/commands/usage.help b/commands/usage.help index 322536c..0059778 100755 --- a/commands/usage.help +++ b/commands/usage.help @@ -49,6 +49,8 @@ RollDev version $(cat ${ROLL_DIR}/version) svc Orchestrates global services such as traefik, portainer and dnsmasq via docker-compose env-init Configure environment by adding \033[31m'.env.roll'\033[0m file to the current working directory env Controls an environment from any point within the root project directory + config Manage and validate Roll configuration (see \033[31m'roll config -h'\033[0m for details) + registry Manage and inspect command registry (see \033[31m'roll registry -h'\033[0m for details) db Interacts with the db service on an environment (see \033[31m'roll db -h'\033[0m for details) redis Interacts with the redis service on an environment (see \033[31m'roll redis -h'\033[0m for details) install Initializes or updates roll configuration on host machine @@ -75,3 +77,5 @@ ${ENV_TYPE_USAGE:1} ${ROLL_DARWIN:1} EOF ) + +echo -e "${ROLL_USAGE}" diff --git a/commands/version.help b/commands/version.help new file mode 100644 index 0000000..58a065d --- /dev/null +++ b/commands/version.help @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +ROLL_USAGE=$(cat < [options] +``` + +### List Commands + +Display all registered commands: + +```bash +roll registry list +``` + +Filter commands by name pattern: + +```bash +roll registry list config +``` + +Filter commands by category: + +```bash +roll registry list "" environment +``` + +### Browse by Category + +View commands organized by category: + +```bash +roll registry categories +``` + +Show commands in a specific category: + +```bash +roll registry categories environment +``` + +### Command Information + +Get detailed information about a specific command: + +```bash +roll registry info config +``` + +This displays: +* Command file path +* Help file path +* Category +* Priority level +* Description + +### Search Commands + +Search commands by name or description: + +```bash +roll registry search database +roll registry search ssl +``` + +### Registry Statistics + +View registry statistics and command counts: + +```bash +roll registry stats +``` + +### Validate Registry + +Check registry integrity and validate all command files: + +```bash +roll registry validate +``` + +This checks for: +* Missing command files +* Missing help files (warnings only) +* Registry consistency + +### Export Commands + +Export command list in various formats: + +```bash +# Simple list (default) +roll registry export simple + +# JSON format +roll registry export json + +# CSV format +roll registry export csv +``` + +### View Search Paths + +Display command search paths and their priorities: + +```bash +roll registry paths +``` + +### Refresh Registry + +Refresh the command registry cache: + +```bash +roll registry refresh +``` + +## Command Discovery + +The registry system searches for commands in multiple directories with priority-based resolution: + +### Search Path Priority + +1. **Priority 1**: Project-local commands (`.roll/commands` in project directory) +2. **Priority 1**: Environment-specific commands (`~/.roll/reclu/{env_type}`) +3. **Priority 2**: User home commands (`~/.roll/commands`) +4. **Priority 3**: User reclu commands (`~/.roll/reclu`) +5. **Priority 4**: System commands (`{roll_install}/commands`) + +Lower priority numbers have higher precedence. This allows for easy command customization and overrides. + +### Environment-Specific Discovery + +The registry automatically includes environment-specific commands when an environment is loaded: + +* Commands from `~/.roll/reclu/{environment_type}` (e.g., `~/.roll/reclu/magento2`) +* Commands from `{roll_install}/commands/{environment_type}` +* Project-local commands from `.roll/commands` + +## Command Categories + +Commands are automatically categorized based on their help file metadata or directory structure: + +* **Environment Setup**: Installation and initialization commands +* **Environment Management**: Start, stop, configuration commands +* **Development Tools**: Database, debugging, shell access +* **Information**: Version, help, status commands +* **General**: Uncategorized commands + +### Setting Command Category + +Add a category comment to your command's help file: + +```bash +#!/usr/bin/env bash +# Category: Development Tools + +ROLL_USAGE=$(cat <` comments in help files +2. `# TYPE: ` comments in help files +3. Directory-based categorization + +## Creating Custom Commands + +### Command File Structure + +Create a command file with the `.cmd` extension: + +```bash +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +# Your command logic here +echo "Hello from custom command!" +``` + +### Help File Structure + +Create a corresponding `.help` file: + +```bash +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +ROLL_USAGE=$(cat < commands.json +``` + +## Troubleshooting + +### Registry Validation Fails + +If `roll registry validate` shows errors: + +1. Check that command files exist and are executable +2. Verify help files follow the correct format +3. Ensure directory permissions are correct + +### Commands Not Found + +If commands aren't being discovered: + +1. Run `roll registry refresh` to clear the cache +2. Check `roll registry paths` to verify search directories +3. Verify file extensions are `.cmd` for commands and `.help` for help files + +### Performance Issues + +If command discovery is slow: + +1. Reduce the number of directories in search paths +2. Remove unused command directories +3. Use `roll registry stats` to check command counts + +## Migration from Legacy System + +The registry system is fully backward compatible. No migration is required, but you can: + +1. Run `roll registry validate` to check for missing help files +2. Add category metadata to help files for better organization +3. Use `roll registry stats` to understand your command inventory + +For more information, run `roll registry --help` or `roll registry --help` for specific command details. \ No newline at end of file diff --git a/utils/config.sh b/utils/config.sh new file mode 100644 index 0000000..a6f5d40 --- /dev/null +++ b/utils/config.sh @@ -0,0 +1,558 @@ +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +## Configuration Management System +## Compatible with Bash 3.2+ (macOS default) +## Cross-platform: Linux, macOS, WSL + +# Configuration cache using simple arrays instead of associative arrays for Bash 3.2 compatibility +ROLL_CONFIG_CACHE_KEYS=() +ROLL_CONFIG_CACHE_VALUES=() +ROLL_CONFIG_LOADED_FILES=() + +# Configuration schema using indexed arrays +ROLL_CONFIG_SCHEMA_KEYS=() +ROLL_CONFIG_SCHEMA_VALUES=() + +## Helper function to find index of key in array +function findConfigIndex() { + local key="$1" + local i=0 + for cached_key in "${ROLL_CONFIG_CACHE_KEYS[@]}"; do + if [[ "$cached_key" == "$key" ]]; then + echo $i + return 0 + fi + i=$((i + 1)) + done + echo -1 +} + +## Helper function to find schema index +function findSchemaIndex() { + local key="$1" + local i=0 + for schema_key in "${ROLL_CONFIG_SCHEMA_KEYS[@]}"; do + if [[ "$schema_key" == "$key" ]]; then + echo $i + return 0 + fi + i=$((i + 1)) + done + echo -1 +} + +## Helper function to check if file is loaded +function isFileLoaded() { + local file="$1" + local loaded_file + for loaded_file in "${ROLL_CONFIG_LOADED_FILES[@]}"; do + if [[ "$loaded_file" == "$file" ]]; then + return 0 + fi + done + return 1 +} + +# Initialize configuration schema +function initConfigSchema() { + # Skip if already initialized + if [[ ${#ROLL_CONFIG_SCHEMA_KEYS[@]} -gt 0 ]]; then + return 0 + fi + + # Core Roll configuration + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ENV_NAME); ROLL_CONFIG_SCHEMA_VALUES+=("string:required") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ENV_TYPE); ROLL_CONFIG_SCHEMA_VALUES+=("string:required") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ENV_SUBT); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + + # Service toggles (boolean with defaults) + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_NGINX); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:1") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_DB); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:1") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_REDIS); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:1") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_DRAGONFLY); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_VARNISH); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ELASTICSEARCH); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_OPENSEARCH); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ELASTICVUE); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_RABBITMQ); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_MONGODB); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_BROWSERSYNC); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_SELENIUM); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_SELENIUM_DEBUG); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_TEST_DB); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ALLURE); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_MAGEPACK); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_INCLUDE_GIT); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + + # Traefik configuration + ROLL_CONFIG_SCHEMA_KEYS+=(TRAEFIK_DOMAIN); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + ROLL_CONFIG_SCHEMA_KEYS+=(TRAEFIK_SUBDOMAIN); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + ROLL_CONFIG_SCHEMA_KEYS+=(TRAEFIK_LISTEN); ROLL_CONFIG_SCHEMA_VALUES+=("string:127.0.0.1") + + # PHP configuration + ROLL_CONFIG_SCHEMA_KEYS+=(PHP_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:8.1") + ROLL_CONFIG_SCHEMA_KEYS+=(PHP_XDEBUG_3); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:1") + ROLL_CONFIG_SCHEMA_KEYS+=(PHP_MEMORY_LIMIT); ROLL_CONFIG_SCHEMA_VALUES+=("string:2G") + + # Composer configuration + ROLL_CONFIG_SCHEMA_KEYS+=(COMPOSER_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + + # Database configuration + ROLL_CONFIG_SCHEMA_KEYS+=(DB_DISTRIBUTION); ROLL_CONFIG_SCHEMA_VALUES+=("string:mariadb") + ROLL_CONFIG_SCHEMA_KEYS+=(DB_DISTRIBUTION_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:10.4") + ROLL_CONFIG_SCHEMA_KEYS+=(MYSQL_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:8.0") + ROLL_CONFIG_SCHEMA_KEYS+=(MARIADB_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:10.4") + + # Service version configurations + ROLL_CONFIG_SCHEMA_KEYS+=(ELASTICSEARCH_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:7.17") + ROLL_CONFIG_SCHEMA_KEYS+=(RABBITMQ_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:3.11") + ROLL_CONFIG_SCHEMA_KEYS+=(REDIS_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:7.0") + ROLL_CONFIG_SCHEMA_KEYS+=(DRAGONFLY_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:latest") + ROLL_CONFIG_SCHEMA_KEYS+=(VARNISH_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:7.0") + ROLL_CONFIG_SCHEMA_KEYS+=(OPENSEARCH_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:2.5") + ROLL_CONFIG_SCHEMA_KEYS+=(MONGO_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:6.0") + ROLL_CONFIG_SCHEMA_KEYS+=(NGINX_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:1.24") + ROLL_CONFIG_SCHEMA_KEYS+=(MAGEPACK_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:2.3") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_SELENIUM_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:3.141.59") + + # Node configuration + ROLL_CONFIG_SCHEMA_KEYS+=(NODE_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:18") + + # Nginx configuration + ROLL_CONFIG_SCHEMA_KEYS+=(NGINX_TEMPLATE); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + ROLL_CONFIG_SCHEMA_KEYS+=(NGINX_PUBLIC); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + + # Magento specific + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ADMIN_AUTOLOGIN); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_MAGENTO_STATIC_CACHING); ROLL_CONFIG_SCHEMA_VALUES+=("boolean:0") + + # Environment paths and directories + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_WEB_ROOT); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_SYNC_IGNORE); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_CHOWN_DIR_LIST); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + + # Extensions and customizations + ROLL_CONFIG_SCHEMA_KEYS+=(ADD_PHP_EXT); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + + # Container configuration + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ENV_SHELL_CONTAINER); ROLL_CONFIG_SCHEMA_VALUES+=("string:php-fpm") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ENV_SHELL_COMMAND); ROLL_CONFIG_SCHEMA_VALUES+=("string:bash") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_ENV_SHELL_DEBUG_CONTAINER); ROLL_CONFIG_SCHEMA_VALUES+=("string:php-debug") + + # XDebug configuration + ROLL_CONFIG_SCHEMA_KEYS+=(XDEBUG_CONNECT_BACK_HOST); ROLL_CONFIG_SCHEMA_VALUES+=("string:optional") + ROLL_CONFIG_SCHEMA_KEYS+=(XDEBUG_VERSION); ROLL_CONFIG_SCHEMA_VALUES+=("string:debug") + + # System configuration + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_RESTART_POLICY); ROLL_CONFIG_SCHEMA_VALUES+=("string:always") + ROLL_CONFIG_SCHEMA_KEYS+=(ROLL_IMAGE_REPOSITORY); ROLL_CONFIG_SCHEMA_VALUES+=("string:ghcr.io/dockergiant") +} + +## Get schema for a key +function getSchema() { + local key="$1" + local index=$(findSchemaIndex "$key") + if [[ $index -ge 0 ]]; then + echo "${ROLL_CONFIG_SCHEMA_VALUES[$index]}" + fi +} + +## Validate configuration value against schema +function validateConfigValue() { + local key="$1" + local value="$2" + local schema="$(getSchema "$key")" + + if [[ -z "$schema" ]]; then + # Unknown configuration key - allow but warn + warning "Unknown configuration key: $key" + return 0 + fi + + local type="${schema%%:*}" + local constraint="${schema##*:}" + + case "$type" in + boolean) + if [[ "$value" != "0" && "$value" != "1" ]]; then + error "Configuration $key must be 0 or 1, got: $value" + return 1 + fi + ;; + string) + if [[ "$constraint" == "required" && -z "$value" ]]; then + error "Configuration $key is required but empty" + return 1 + fi + ;; + integer) + if ! [[ "$value" =~ ^[0-9]+$ ]]; then + error "Configuration $key must be an integer, got: $value" + return 1 + fi + ;; + esac + + return 0 +} + +## Set default value for configuration if not set +function setConfigDefault() { + local key="$1" + local schema="$(getSchema "$key")" + + if [[ -z "$schema" ]]; then + return 0 + fi + + local constraint="${schema##*:}" + + # Skip if already set or no default available + local index=$(findConfigIndex "$key") + if [[ $index -ge 0 || "$constraint" == "required" || "$constraint" == "optional" ]]; then + return 0 + fi + + # Set default value + ROLL_CONFIG_CACHE_KEYS+=("$key") + ROLL_CONFIG_CACHE_VALUES+=("$constraint") + export "$key"="$constraint" +} + +## Load configuration from file with validation +function loadConfigFromFile() { + local config_file="$1" + local validate_only="${2:-false}" + + if [[ ! -f "$config_file" ]]; then + error "Configuration file not found: $config_file" + return 1 + fi + + # Check if already loaded + if isFileLoaded "$config_file" && [[ "$validate_only" == "false" ]]; then + return 0 + fi + + local line_num=0 + local errors=0 + + while IFS= read -r line || [[ -n "$line" ]]; do + line_num=$((line_num + 1)) + + # Skip empty lines and comments + [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue + + # Remove Windows line endings + line="${line%$'\r'}" + + # Parse key=value pairs + if [[ "$line" =~ ^[[:space:]]*([A-Z_][A-Z0-9_]*)=(.*)$ ]]; then + local key="${BASH_REMATCH[1]}" + local value="${BASH_REMATCH[2]}" + + # Remove quotes if present + if [[ "$value" =~ ^\"(.*)\"$ ]] || [[ "$value" =~ ^\'(.*)\'$ ]]; then + value="${BASH_REMATCH[1]}" + fi + + # Validate configuration + if ! validateConfigValue "$key" "$value"; then + error "Invalid configuration at $config_file:$line_num" + errors=$((errors + 1)) + continue + fi + + # Store in cache and export if not validation-only + if [[ "$validate_only" == "false" ]]; then + local index=$(findConfigIndex "$key") + if [[ $index -ge 0 ]]; then + # Update existing + ROLL_CONFIG_CACHE_VALUES[$index]="$value" + else + # Add new + ROLL_CONFIG_CACHE_KEYS+=("$key") + ROLL_CONFIG_CACHE_VALUES+=("$value") + fi + export "$key"="$value" + fi + + elif [[ "$line" =~ ^[[:space:]]*[^=]+$ ]]; then + warning "Invalid configuration line at $config_file:$line_num: $line" + fi + + done < "$config_file" + + if [[ $errors -gt 0 ]]; then + return 1 + fi + + # Mark as loaded + if [[ "$validate_only" == "false" ]]; then + ROLL_CONFIG_LOADED_FILES+=("$config_file") + fi + + return 0 +} + +## Load Roll environment configuration +function loadRollConfig() { + local config_path="$1" + + if [[ -z "$config_path" ]]; then + config_path="$(locateEnvPath 2>/dev/null)" || { + error "Could not locate environment configuration" + return 1 + } + fi + + local config_file="$config_path/.env.roll" + + # Initialize schema if not done + initConfigSchema + + # Load configuration from file + if ! loadConfigFromFile "$config_file"; then + return 1 + fi + + # Set OS-specific defaults + case "${OSTYPE:-undefined}" in + darwin*) + setConfigValue "ROLL_ENV_SUBT" "darwin" + ;; + linux*) + setConfigValue "ROLL_ENV_SUBT" "linux" + + # Check for WSL + if grep -sqi microsoft /proc/sys/kernel/osrelease 2>/dev/null; then + setConfigValue "ROLL_ENV_SUBT" "wsl" + fi + ;; + *) + error "Unsupported OSTYPE '${OSTYPE:-undefined}'" + return 1 + ;; + esac + + # Set system-specific exports + export USER_ID="$(id -u)" + export GROUP_ID="$(id -g)" + + # Set defaults for unset values + local i=0 + while [[ $i -lt ${#ROLL_CONFIG_SCHEMA_KEYS[@]} ]]; do + setConfigDefault "${ROLL_CONFIG_SCHEMA_KEYS[$i]}" + i=$((i + 1)) + done + + # Validate environment type + if ! assertValidEnvType; then + return 1 + fi + + # Post-processing for specific configurations + postProcessConfig + + return 0 +} + +## Set configuration value +function setConfigValue() { + local key="$1" + local value="$2" + + local index=$(findConfigIndex "$key") + if [[ $index -ge 0 ]]; then + # Update existing + ROLL_CONFIG_CACHE_VALUES[$index]="$value" + else + # Add new + ROLL_CONFIG_CACHE_KEYS+=("$key") + ROLL_CONFIG_CACHE_VALUES+=("$value") + fi + export "$key"="$value" +} + +## Post-process configuration after loading +function postProcessConfig() { + # Set PHP variant based on environment type + if [[ "${ROLL_ENV_TYPE}" =~ ^magento ]] || [[ "${ROLL_ENV_TYPE}" =~ ^wordpress ]]; then + export ROLL_SVC_PHP_VARIANT="-${ROLL_ENV_TYPE}" + fi + + # Set Node.js variant + if [[ "${NODE_VERSION}" != "0" ]]; then + export ROLL_SVC_PHP_NODE="-node${NODE_VERSION}" + fi + + # Database distribution defaults + if [[ -z "${DB_DISTRIBUTION_VERSION}" ]]; then + if [[ "${DB_DISTRIBUTION}" == "mysql" ]]; then + export DB_DISTRIBUTION_VERSION="${MYSQL_VERSION:-8.0}" + else + export DB_DISTRIBUTION_VERSION="${MARIADB_VERSION:-10.4}" + fi + fi + + # XDebug version configuration + if [[ "${PHP_XDEBUG_3}" == "1" ]]; then + export XDEBUG_VERSION="xdebug3" + else + export XDEBUG_VERSION="debug" + fi + + # WSL XDebug host configuration + if [[ "${ROLL_ENV_SUBT}" == "wsl" && -z "${XDEBUG_CONNECT_BACK_HOST}" ]]; then + export XDEBUG_CONNECT_BACK_HOST="host.docker.internal" + fi + + # Linux SSH auth sock path + if [[ "${ROLL_ENV_SUBT}" == "linux" && "$(id -u)" == "1000" ]]; then + export SSH_AUTH_SOCK_PATH_ENV="/run/host-services/ssh-auth.sock" + fi + + # Environment-specific defaults + if [[ "${ROLL_ENV_TYPE}" != "local" ]]; then + export ROLL_NGINX="${ROLL_NGINX:-1}" + export ROLL_DB="${ROLL_DB:-1}" + export ROLL_REDIS="${ROLL_REDIS:-1}" + + # Bash history and SSH directories + export CHOWN_DIR_LIST="/bash_history /home/www-data/.ssh ${ROLL_CHOWN_DIR_LIST:-}" + fi + + # Magento 1 specific configuration + if [[ "${ROLL_ENV_TYPE}" == "magento1" ]]; then + if [[ -f "${ROLL_ENV_PATH}/.modman/.basedir" ]]; then + export NGINX_PUBLIC="/$(cat "${ROLL_ENV_PATH}/.modman/.basedir")" + fi + + if [[ "${ROLL_MAGENTO_STATIC_CACHING}" == "1" ]]; then + export NGINX_TEMPLATE="${NGINX_TEMPLATE:-magento1.conf}" + else + export NGINX_TEMPLATE="${NGINX_TEMPLATE:-magento1-dev.conf}" + fi + fi + + # Magento 2 specific configuration + if [[ "${ROLL_ENV_TYPE}" == "magento2" ]]; then + export ROLL_VARNISH="${ROLL_VARNISH:-1}" + export ROLL_ELASTICSEARCH="${ROLL_ELASTICSEARCH:-1}" + export ROLL_RABBITMQ="${ROLL_RABBITMQ:-1}" + + if [[ "${ROLL_MAGENTO_STATIC_CACHING}" == "1" ]]; then + if [[ "${ROLL_ADMIN_AUTOLOGIN}" == "1" ]]; then + export NGINX_TEMPLATE="${NGINX_TEMPLATE:-magento2-autologin.conf}" + else + export NGINX_TEMPLATE="${NGINX_TEMPLATE:-magento2.conf}" + fi + else + if [[ "${ROLL_ADMIN_AUTOLOGIN}" == "1" ]]; then + export NGINX_TEMPLATE="${NGINX_TEMPLATE:-magento2-dev-autologin.conf}" + else + export NGINX_TEMPLATE="${NGINX_TEMPLATE:-magento2-dev.conf}" + fi + fi + fi +} + +## Validate configuration file without loading +function validateConfig() { + local config_file="$1" + + if [[ -z "$config_file" ]]; then + config_file="$(locateEnvPath)/.env.roll" + fi + + # Initialize schema if not done + initConfigSchema + + loadConfigFromFile "$config_file" "true" +} + +## Get configuration value +function getConfig() { + local key="$1" + local default_value="$2" + + local index=$(findConfigIndex "$key") + if [[ $index -ge 0 ]]; then + echo "${ROLL_CONFIG_CACHE_VALUES[$index]}" + elif [[ -n "${!key}" ]]; then + echo "${!key}" + else + echo "${default_value}" + fi +} + +## Set configuration value +function setConfig() { + local key="$1" + local value="$2" + + if validateConfigValue "$key" "$value"; then + setConfigValue "$key" "$value" + return 0 + else + return 1 + fi +} + +## Display configuration summary +function showConfig() { + local filter="${1:-}" + + echo -e "\033[33mRoll Configuration:\033[0m" + echo "Environment: ${ROLL_ENV_NAME:-} (${ROLL_ENV_TYPE:-})" + echo "Platform: ${ROLL_ENV_SUBT:-}" + echo "" + + local i=0 + while [[ $i -lt ${#ROLL_CONFIG_CACHE_KEYS[@]} ]]; do + local key="${ROLL_CONFIG_CACHE_KEYS[$i]}" + local value="${ROLL_CONFIG_CACHE_VALUES[$i]}" + + if [[ -n "$filter" && ! "$key" =~ $filter ]]; then + i=$((i + 1)) + continue + fi + + printf " %-30s = %s\n" "$key" "$value" + i=$((i + 1)) + done +} + +## Check for configuration conflicts +function checkConfigConflicts() { + local errors=0 + + # Redis vs Dragonfly conflict + if [[ "$(getConfig ROLL_REDIS 0)" == "1" && "$(getConfig ROLL_DRAGONFLY 0)" == "1" ]]; then + error "Configuration conflict: ROLL_REDIS and ROLL_DRAGONFLY cannot both be enabled" + errors=$((errors + 1)) + fi + + # Environment type specific validations + if [[ "${ROLL_ENV_TYPE}" == "magento2" ]]; then + if [[ "$(getConfig ROLL_ELASTICSEARCH 0)" == "1" && "$(getConfig ROLL_OPENSEARCH 0)" == "1" ]]; then + warning "Both Elasticsearch and OpenSearch are enabled - this may cause conflicts" + fi + fi + + # Database distribution validation + local db_dist="$(getConfig DB_DISTRIBUTION mariadb)" + if [[ "$db_dist" != "mysql" && "$db_dist" != "mariadb" ]]; then + error "DB_DISTRIBUTION must be either 'mysql' or 'mariadb', got: $db_dist" + errors=$((errors + 1)) + fi + + return $errors +} + +## Legacy compatibility wrapper - replace old loadEnvConfig calls +function loadEnvConfig() { + local env_path="$1" + loadRollConfig "$env_path" +} \ No newline at end of file diff --git a/utils/core.sh b/utils/core.sh index 410fca0..fb31a62 100644 --- a/utils/core.sh +++ b/utils/core.sh @@ -121,6 +121,7 @@ function disconnectPeeredServices { done } -function isOnline { - ping -q -c1 google.com &>/dev/null && echo "true" || echo "false" +# Main logic with the timeout function +function isOnline() { + (ping -q -c1 -t 2 8.8.8.8 &>/dev/null && echo "true") || (ping -q -c1 -t 2 1.1.1.1 &>/dev/null && echo "true") || echo "false" } \ No newline at end of file diff --git a/utils/env.sh b/utils/env.sh index 88b4895..0d54070 100644 --- a/utils/env.sh +++ b/utils/env.sh @@ -31,39 +31,19 @@ function locateEnvPath () { echo "${ROLL_ENV_PATH}" } +# Legacy function - now uses centralized config system function loadEnvConfig () { local ROLL_ENV_PATH="${1}" - eval "$(cat "${ROLL_ENV_PATH}/.env.roll" | sed 's/\r$//g' | grep "^ROLL_")" - eval "$(cat "${ROLL_ENV_PATH}/.env.roll" | sed 's/\r$//g' | grep "^TRAEFIK_")" - eval "$(cat "${ROLL_ENV_PATH}/.env.roll" | sed 's/\r$//g' | grep "^PHP_")" - eval "$(cat "${ROLL_ENV_PATH}/.env.roll" | sed 's/\r$//g' | grep "^NGINX_")" - eval "$(cat "${ROLL_ENV_PATH}/.env.roll" | sed 's/\r$//g' | grep "_VERSION")" - eval "$(cat "${ROLL_ENV_PATH}/.env.roll" | sed 's/\r$//g' | grep "^DB_")" - eval "$(cat "${ROLL_ENV_PATH}/.env.roll" | sed 's/\r$//g' | grep "ADD_PHP_EXT")" - - - ROLL_ENV_NAME="${ROLL_ENV_NAME:-}" - ROLL_ENV_TYPE="${ROLL_ENV_TYPE:-}" - ROLL_ENV_SUBT="" - - case "${OSTYPE:-undefined}" in - darwin*) - ROLL_ENV_SUBT=darwin - ;; - linux*) - ROLL_ENV_SUBT=linux - ;; - *) - fatal "Unsupported OSTYPE '${OSTYPE:-undefined}'" - ;; - esac - - export USER_ID=$(id -u $USER) - export GROUP_ID=$(id -g $USER) - export OSTYPE=$OSTYPE - export ADD_PHP_EXT=$ADD_PHP_EXT - - assertValidEnvType + + # Load new centralized configuration + if ! loadRollConfig "${ROLL_ENV_PATH}"; then + return 1 + fi + + # Set global environment path for backward compatibility + export ROLL_ENV_PATH="${ROLL_ENV_PATH}" + + return 0 } function renderEnvNetworkName() { diff --git a/utils/registry.sh b/utils/registry.sh new file mode 100644 index 0000000..96b56d5 --- /dev/null +++ b/utils/registry.sh @@ -0,0 +1,415 @@ +#!/usr/bin/env bash +[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 + +## Command Registry System +## Discovers and manages commands across all Roll directories +## Compatible with Bash 3.2+ (macOS default) + +# Command registry cache using indexed arrays for Bash 3.2 compatibility +ROLL_REGISTRY_COMMANDS=() +ROLL_REGISTRY_PATHS=() +ROLL_REGISTRY_HELP_PATHS=() +ROLL_REGISTRY_CATEGORIES=() +ROLL_REGISTRY_DESCRIPTIONS=() +ROLL_REGISTRY_PRIORITIES=() +ROLL_REGISTRY_INITIALIZED=0 + +# Command search paths with priorities (lower number = higher priority) +ROLL_COMMAND_SEARCH_PATHS=( + "2:${ROLL_HOME_DIR}/commands" + "3:${ROLL_HOME_DIR}/reclu" + "4:${ROLL_DIR}/commands" +) + +# Environment-specific command paths (added dynamically if env is available) +function getEnvCommandPaths() { + local env_paths=() + + # Add project-local commands if ROLL_ENV_PATH is available + if [[ -n "${ROLL_ENV_PATH}" && -d "${ROLL_ENV_PATH}/.roll/commands" ]]; then + env_paths+=("1:${ROLL_ENV_PATH}/.roll/commands") + fi + + # Add environment-specific commands if ROLL_ENV_TYPE is available + if [[ -n "${ROLL_ENV_TYPE}" ]]; then + [[ -d "${ROLL_HOME_DIR}/reclu/${ROLL_ENV_TYPE}" ]] && env_paths+=("1:${ROLL_HOME_DIR}/reclu/${ROLL_ENV_TYPE}") + [[ -d "${ROLL_DIR}/commands/${ROLL_ENV_TYPE}" ]] && env_paths+=("2:${ROLL_DIR}/commands/${ROLL_ENV_TYPE}") + fi + + printf '%s\n' "${env_paths[@]}" +} + +## Helper function to find command index in registry +function findCommandIndex() { + local command="$1" + local i=0 + for registered_command in "${ROLL_REGISTRY_COMMANDS[@]}"; do + if [[ "$registered_command" == "$command" ]]; then + echo $i + return 0 + fi + i=$((i + 1)) + done + echo -1 +} + +## Extract command metadata from help file +function extractCommandMetadata() { + local help_file="$1" + local metadata_type="$2" + + if [[ ! -f "$help_file" ]]; then + echo "" + return 0 + fi + + case "$metadata_type" in + description) + # Simple approach - just return empty for now + echo "" + ;; + category) + # Simple approach - just return general for now + echo "general" + ;; + *) + echo "" + ;; + esac + + return 0 +} + +## Register a single command in the registry +function registerCommand() { + local command="$1" + local cmd_path="$2" + local help_path="$3" + local priority="$4" + local category="${5:-general}" + + local existing_index=$(findCommandIndex "$command") + + if [[ $existing_index -ge 0 ]]; then + # Command already exists, check priority + local existing_priority="${ROLL_REGISTRY_PRIORITIES[$existing_index]}" + if [[ $priority -lt $existing_priority ]]; then + # New command has higher priority, replace it + ROLL_REGISTRY_PATHS[$existing_index]="$cmd_path" + ROLL_REGISTRY_HELP_PATHS[$existing_index]="$help_path" + ROLL_REGISTRY_PRIORITIES[$existing_index]="$priority" + ROLL_REGISTRY_CATEGORIES[$existing_index]="$category" + ROLL_REGISTRY_DESCRIPTIONS[$existing_index]="$(extractCommandMetadata "$help_path" "description")" + fi + else + # New command, add to registry + ROLL_REGISTRY_COMMANDS+=("$command") + ROLL_REGISTRY_PATHS+=("$cmd_path") + ROLL_REGISTRY_HELP_PATHS+=("$help_path") + ROLL_REGISTRY_PRIORITIES+=("$priority") + ROLL_REGISTRY_CATEGORIES+=("$category") + ROLL_REGISTRY_DESCRIPTIONS+=("$(extractCommandMetadata "$help_path" "description")") + fi +} + +## Scan a directory for commands +function scanCommandDirectory() { + local search_entry="$1" + local priority="${search_entry%%:*}" + local directory="${search_entry##*:}" + local category="${2:-general}" + + # Skip if directory doesn't exist + if [[ ! -d "$directory" ]]; then + return 0 + fi + + local cmd_file help_file command_name + + # Find all .cmd files in directory + for cmd_file in "$directory"/*.cmd; do + # Skip if no .cmd files found (glob didn't match) + [[ ! -f "$cmd_file" ]] && continue + + command_name="$(basename "$cmd_file" .cmd)" + help_file="$directory/$command_name.help" + + # Extract category from help file if available + if [[ -f "$help_file" ]]; then + local extracted_category="$(extractCommandMetadata "$help_file" "category")" + [[ -n "$extracted_category" && "$extracted_category" != "general" ]] && category="$extracted_category" + fi + + registerCommand "$command_name" "$cmd_file" "$help_file" "$priority" "$category" + done +} + +## Initialize command registry by scanning all directories +function initializeRegistry() { + # Skip if already initialized + if [[ $ROLL_REGISTRY_INITIALIZED -eq 1 ]]; then + return 0 + fi + + # Clear existing registry + ROLL_REGISTRY_COMMANDS=() + ROLL_REGISTRY_PATHS=() + ROLL_REGISTRY_HELP_PATHS=() + ROLL_REGISTRY_CATEGORIES=() + ROLL_REGISTRY_DESCRIPTIONS=() + ROLL_REGISTRY_PRIORITIES=() + + # Scan environment-specific directories first (highest priority) + while IFS= read -r env_path; do + [[ -n "$env_path" ]] && scanCommandDirectory "$env_path" "environment" + done < <(getEnvCommandPaths) + + # Scan global command directories + local search_path + for search_path in "${ROLL_COMMAND_SEARCH_PATHS[@]}"; do + scanCommandDirectory "$search_path" "global" + done + + ROLL_REGISTRY_INITIALIZED=1 +} + +## Get command information from registry +function getCommandInfo() { + local command="$1" + local info_type="$2" + + local index=$(findCommandIndex "$command") + [[ $index -eq -1 ]] && return 1 + + case "$info_type" in + path) + echo "${ROLL_REGISTRY_PATHS[$index]}" + ;; + help) + echo "${ROLL_REGISTRY_HELP_PATHS[$index]}" + ;; + category) + echo "${ROLL_REGISTRY_CATEGORIES[$index]}" + ;; + description) + echo "${ROLL_REGISTRY_DESCRIPTIONS[$index]}" + ;; + priority) + echo "${ROLL_REGISTRY_PRIORITIES[$index]}" + ;; + *) + return 1 + ;; + esac +} + +## Check if command exists in registry +function isCommandRegistered() { + local command="$1" + local index=$(findCommandIndex "$command") + [[ $index -ge 0 ]] +} + +## List all registered commands +function listRegisteredCommands() { + local filter="${1:-}" + local category_filter="${2:-}" + + local i=0 + while [[ $i -lt ${#ROLL_REGISTRY_COMMANDS[@]} ]]; do + local command="${ROLL_REGISTRY_COMMANDS[$i]}" + local category="${ROLL_REGISTRY_CATEGORIES[$i]}" + + # Apply filters + if [[ -n "$filter" && ! "$command" =~ $filter ]]; then + i=$((i + 1)) + continue + fi + + if [[ -n "$category_filter" && "$category" != "$category_filter" ]]; then + i=$((i + 1)) + continue + fi + + echo "$command" + i=$((i + 1)) + done +} + +## List commands by category +function listCommandsByCategory() { + local target_category="${1:-}" + + # Get unique categories if no specific category requested + if [[ -z "$target_category" ]]; then + local categories=() + local i=0 + while [[ $i -lt ${#ROLL_REGISTRY_CATEGORIES[@]} ]]; do + local category="${ROLL_REGISTRY_CATEGORIES[$i]}" + local found=0 + local existing_category + for existing_category in "${categories[@]}"; do + if [[ "$existing_category" == "$category" ]]; then + found=1 + break + fi + done + [[ $found -eq 0 ]] && categories+=("$category") + i=$((i + 1)) + done + + # Display all categories + for category in "${categories[@]}"; do + echo -e "\033[33m${category^} Commands:\033[0m" + listCommandsByCategory "$category" + echo "" + done + return 0 + fi + + # List commands in specific category + local i=0 + while [[ $i -lt ${#ROLL_REGISTRY_COMMANDS[@]} ]]; do + local command="${ROLL_REGISTRY_COMMANDS[$i]}" + local category="${ROLL_REGISTRY_CATEGORIES[$i]}" + local description="${ROLL_REGISTRY_DESCRIPTIONS[$i]}" + + if [[ "$category" == "$target_category" ]]; then + printf " %-20s %s\n" "$command" "$description" + fi + i=$((i + 1)) + done +} + +## Find command and return its execution details +function findCommand() { + local command="$1" + + # Initialize registry if needed + initializeRegistry + + # Check registry first + if isCommandRegistered "$command"; then + local cmd_path="$(getCommandInfo "$command" "path")" + local help_path="$(getCommandInfo "$command" "help")" + + # Return in format: "found:cmd_path:help_path" + echo "found:$cmd_path:$help_path" + return 0 + fi + + # Command not found + echo "notfound" + return 0 +} + +## Refresh registry (useful after adding new commands) +function refreshRegistry() { + ROLL_REGISTRY_INITIALIZED=0 + initializeRegistry +} + +## Display registry statistics +function showRegistryStats() { + initializeRegistry + + echo -e "\033[33mCommand Registry Statistics:\033[0m" + echo " Total commands: ${#ROLL_REGISTRY_COMMANDS[@]}" + + # Count by category + local categories=() + local category_counts=() + local i=0 + + while [[ $i -lt ${#ROLL_REGISTRY_CATEGORIES[@]} ]]; do + local category="${ROLL_REGISTRY_CATEGORIES[$i]}" + local found_index=-1 + local j=0 + + # Find existing category + for existing_category in "${categories[@]}"; do + if [[ "$existing_category" == "$category" ]]; then + found_index=$j + break + fi + j=$((j + 1)) + done + + if [[ $found_index -ge 0 ]]; then + # Increment existing category count + category_counts[$found_index]=$((${category_counts[$found_index]} + 1)) + else + # Add new category + categories+=("$category") + category_counts+=(1) + fi + + i=$((i + 1)) + done + + # Display category counts + i=0 + while [[ $i -lt ${#categories[@]} ]]; do + printf " %-15s: %d commands\n" "${categories[$i]^}" "${category_counts[$i]}" + i=$((i + 1)) + done +} + +## Export command list for external tools +function exportCommands() { + local format="${1:-simple}" + + initializeRegistry + + case "$format" in + json) + echo "[" + local i=0 + while [[ $i -lt ${#ROLL_REGISTRY_COMMANDS[@]} ]]; do + local command="${ROLL_REGISTRY_COMMANDS[$i]}" + local path="${ROLL_REGISTRY_PATHS[$i]}" + local help_path="${ROLL_REGISTRY_HELP_PATHS[$i]}" + local category="${ROLL_REGISTRY_CATEGORIES[$i]}" + local description="${ROLL_REGISTRY_DESCRIPTIONS[$i]}" + local priority="${ROLL_REGISTRY_PRIORITIES[$i]}" + + echo " {" + echo " \"command\": \"$command\"," + echo " \"path\": \"$path\"," + echo " \"help_path\": \"$help_path\"," + echo " \"category\": \"$category\"," + echo " \"description\": \"$description\"," + echo " \"priority\": $priority" + if [[ $i -eq $((${#ROLL_REGISTRY_COMMANDS[@]} - 1)) ]]; then + echo " }" + else + echo " }," + fi + i=$((i + 1)) + done + echo "]" + ;; + csv) + echo "command,path,help_path,category,description,priority" + local i=0 + while [[ $i -lt ${#ROLL_REGISTRY_COMMANDS[@]} ]]; do + local command="${ROLL_REGISTRY_COMMANDS[$i]}" + local path="${ROLL_REGISTRY_PATHS[$i]}" + local help_path="${ROLL_REGISTRY_HELP_PATHS[$i]}" + local category="${ROLL_REGISTRY_CATEGORIES[$i]}" + local description="${ROLL_REGISTRY_DESCRIPTIONS[$i]}" + local priority="${ROLL_REGISTRY_PRIORITIES[$i]}" + + echo "$command,$path,$help_path,$category,\"$description\",$priority" + i=$((i + 1)) + done + ;; + simple|*) + local i=0 + while [[ $i -lt ${#ROLL_REGISTRY_COMMANDS[@]} ]]; do + echo "${ROLL_REGISTRY_COMMANDS[$i]}" + i=$((i + 1)) + done + ;; + esac +} \ No newline at end of file