#!/bin/bash # # ThrillWiki Automation Configuration Library # Centralized configuration management for bulletproof automation system # # Features: # - Configuration file reading/writing with validation # - GitHub PAT token management and validation # - Environment variable management with secure file permissions # - Configuration migration and backup utilities # - Comprehensive error handling and logging # # [AWS-SECRET-REMOVED]==================================== # LIBRARY METADATA # [AWS-SECRET-REMOVED]==================================== AUTOMATION_CONFIG_VERSION="1.0.0" AUTOMATION_CONFIG_LOADED="true" # [AWS-SECRET-REMOVED]==================================== # CONFIGURATION CONSTANTS # [AWS-SECRET-REMOVED]==================================== # Configuration file paths readonly CONFIG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" readonly SYSTEMD_CONFIG_DIR="$CONFIG_DIR/scripts/systemd" readonly VM_CONFIG_DIR="$CONFIG_DIR/scripts/vm" # Environment configuration files readonly ENV_EXAMPLE_FILE="$SYSTEMD_CONFIG_DIR/thrillwiki-automation***REMOVED***.example" readonly ENV_CONFIG_FILE="$SYSTEMD_CONFIG_DIR/thrillwiki-automation***REMOVED***" readonly PROJECT_ENV_FILE="$CONFIG_DIR/***REMOVED***" # GitHub authentication files readonly GITHUB_TOKEN_FILE="$CONFIG_DIR/.github-pat" readonly GITHUB_AUTH_SCRIPT="$CONFIG_DIR/scripts/github-auth.py" readonly GITHUB_TOKEN_BACKUP="$CONFIG_DIR/.github-pat.backup" # Service configuration readonly SERVICE_NAME="thrillwiki-automation" readonly SERVICE_FILE="$SYSTEMD_CONFIG_DIR/$SERVICE_NAME.service" # Backup configuration readonly CONFIG_BACKUP_DIR="$CONFIG_DIR/backups/config" readonly MAX_BACKUPS=5 # [AWS-SECRET-REMOVED]==================================== # COLOR DEFINITIONS # [AWS-SECRET-REMOVED]==================================== if [[ -z "${RED:-}" ]]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # No Color fi # [AWS-SECRET-REMOVED]==================================== # LOGGING FUNCTIONS # [AWS-SECRET-REMOVED]==================================== # Configuration-specific logging functions config_log() { local level="$1" local color="$2" local message="$3" local timestamp="$(date '+%Y-%m-%d %H:%M:%S')" echo -e "${color}[$timestamp] [CONFIG-$level]${NC} $message" } config_info() { config_log "INFO" "$BLUE" "$1" } config_success() { config_log "SUCCESS" "$GREEN" "✅ $1" } config_warning() { config_log "WARNING" "$YELLOW" "⚠️ $1" } config_error() { config_log "ERROR" "$RED" "❌ $1" } config_debug() { if [[ "${CONFIG_DEBUG:-false}" == "true" ]]; then config_log "DEBUG" "$PURPLE" "🔍 $1" fi } # [AWS-SECRET-REMOVED]==================================== # UTILITY FUNCTIONS # [AWS-SECRET-REMOVED]==================================== # Check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Create directory with proper permissions if it doesn't exist ensure_directory() { local dir="$1" local permissions="${2:-755}" if [[ ! -d "$dir" ]]; then config_debug "Creating directory: $dir" mkdir -p "$dir" chmod "$permissions" "$dir" config_debug "Directory created with permissions $permissions" fi } # Set secure file permissions set_secure_permissions() { local file="$1" local permissions="${2:-600}" if [[ -f "$file" ]]; then chmod "$permissions" "$file" config_debug "Set permissions $permissions on $file" fi } # Backup a file with timestamp backup_file() { local source_file="$1" local backup_dir="${2:-$CONFIG_BACKUP_DIR}" if [[ ! -f "$source_file" ]]; then config_debug "Source file does not exist for backup: $source_file" return 1 fi ensure_directory "$backup_dir" local filename filename=$(basename "$source_file") local timestamp timestamp=$(date '+%Y%m%d_%H%M%S') local backup_file="$backup_dir/${filename}.${timestamp}.backup" if cp "$source_file" "$backup_file"; then config_debug "File backed up: $source_file -> $backup_file" # Clean up old backups (keep only MAX_BACKUPS) local backup_count backup_count=$(find "$backup_dir" -name "${filename}.*.backup" | wc -l) if [[ $backup_count -gt $MAX_BACKUPS ]]; then config_debug "Cleaning up old backups (keeping $MAX_BACKUPS)" find "$backup_dir" -name "${filename}.*.backup" -type f -printf '%T@ %p\n' | \ sort -n | head -n -"$MAX_BACKUPS" | cut -d' ' -f2- | \ xargs rm -f fi echo "$backup_file" return 0 else config_error "Failed to backup file: $source_file" return 1 fi } # [AWS-SECRET-REMOVED]==================================== # CONFIGURATION FILE MANAGEMENT # [AWS-SECRET-REMOVED]==================================== # Read configuration value from file read_config_value() { local key="$1" local config_file="${2:-$ENV_CONFIG_FILE}" local default_value="${3:-}" config_debug "Reading config value: $key from $config_file" if [[ ! -f "$config_file" ]]; then config_debug "Config file not found: $config_file" echo "$default_value" return 1 fi # Look for the key (handle both commented and uncommented lines) local value value=$(grep -E "^[#[:space:]]*${key}[[:space:]]*=" "$config_file" | \ grep -v "^[[:space:]]*#" | \ tail -1 | \ cut -d'=' -f2- | \ sed 's/^[[:space:]]*//' | \ sed 's/[[:space:]]*$//' | \ sed 's/^["'\'']\(.*\)["'\'']$/\1/') if [[ -n "$value" ]]; then echo "$value" return 0 else echo "$default_value" return 1 fi } # Write configuration value to file write_config_value() { local key="$1" local value="$2" local config_file="${3:-$ENV_CONFIG_FILE}" local create_if_missing="${4:-true}" config_debug "Writing config value: $key=$value to $config_file" # Create config file from example if it doesn't exist if [[ ! -f "$config_file" ]] && [[ "$create_if_missing" == "true" ]]; then if [[ -f "$ENV_EXAMPLE_FILE" ]]; then config_info "Creating config file from template: $config_file" cp "$ENV_EXAMPLE_FILE" "$config_file" set_secure_permissions "$config_file" 600 else config_info "Creating new config file: $config_file" touch "$config_file" set_secure_permissions "$config_file" 600 fi fi # Backup existing file backup_file "$config_file" >/dev/null # Check if key already exists if grep -q "^[#[:space:]]*${key}[[:space:]]*=" "$config_file" 2>/dev/null; then # Update existing key config_debug "Updating existing key: $key" # Use a temporary file for safe updating local temp_file temp_file=$(mktemp) # Process the file line by line while IFS= read -r line || [[ -n "$line" ]]; do if [[ "$line" =~ ^[#[:space:]]*${key}[[:space:]]*= ]]; then # Replace this line with the new value echo "$key=$value" config_debug "Replaced line: $line -> $key=$value" else echo "$line" fi done < "$config_file" > "$temp_file" # Replace original file mv "$temp_file" "$config_file" set_secure_permissions "$config_file" 600 else # Add new key config_debug "Adding new key: $key" echo "$key=$value" >> "$config_file" fi config_success "Configuration updated: $key" return 0 } # Remove configuration value from file remove_config_value() { local key="$1" local config_file="${2:-$ENV_CONFIG_FILE}" config_debug "Removing config value: $key from $config_file" if [[ ! -f "$config_file" ]]; then config_warning "Config file not found: $config_file" return 1 fi # Backup existing file backup_file "$config_file" >/dev/null # Remove the key using sed sed -i.tmp "/^[#[:space:]]*${key}[[:space:]]*=/d" "$config_file" rm -f "${config_file}.tmp" config_success "Configuration removed: $key" return 0 } # Validate configuration file validate_config_file() { local config_file="${1:-$ENV_CONFIG_FILE}" local errors=0 config_info "Validating configuration file: $config_file" if [[ ! -f "$config_file" ]]; then config_error "Configuration file not found: $config_file" return 1 fi # Check file permissions local perms perms=$(stat -c "%a" "$config_file" 2>/dev/null || stat -f "%A" "$config_file" 2>/dev/null) if [[ "$perms" != "600" ]] && [[ "$perms" != "0600" ]]; then config_warning "Configuration file has insecure permissions: $perms (should be 600)" ((errors++)) fi # Check for required variables if GitHub token is configured local github_token github_token=$(read_config_value "GITHUB_TOKEN" "$config_file") if [[ -n "$github_token" ]]; then config_debug "GitHub token found in configuration" # Check token format if [[ ! "$github_token" =~ ^gh[pousr]_[A-Za-z0-9_]{36,255}$ ]]; then config_warning "GitHub token format appears invalid" ((errors++)) fi fi # Check syntax by sourcing in a subshell if ! (source "$config_file" >/dev/null 2>&1); then config_error "Configuration file has syntax errors" ((errors++)) fi if [[ $errors -eq 0 ]]; then config_success "Configuration file validation passed" return 0 else config_error "Configuration file validation failed with $errors errors" return 1 fi } # [AWS-SECRET-REMOVED]==================================== # GITHUB PAT TOKEN MANAGEMENT # [AWS-SECRET-REMOVED]==================================== # Validate GitHub PAT token format validate_github_token_format() { local token="$1" if [[ -z "$token" ]]; then config_debug "Empty token provided" return 1 fi # GitHub token formats: # - Classic PAT: ghp_[36-40 chars] # - Fine-grained PAT: github_pat_[40+ chars] # - OAuth token: gho_[36-40 chars] # - User token: ghu_[36-40 chars] # - Server token: ghs_[36-40 chars] # - Refresh token: ghr_[36-40 chars] if [[ "$token" =~ ^gh[pousr]_[A-Za-z0-9_]{36,255}$ ]] || [[ "$token" =~ ^github_pat_[A-Za-z0-9_]{40,255}$ ]]; then config_debug "Token format is valid" return 0 else config_debug "Token format is invalid" return 1 fi } # Test GitHub PAT token by making API call test_github_token() { local token="$1" local timeout="${2:-10}" config_debug "Testing GitHub token with API call" if [[ -z "$token" ]]; then config_error "No token provided for testing" return 1 fi # Test with GitHub API local response local http_code response=$(curl -s -w "%{http_code}" \ --max-time "$timeout" \ -H "Authorization: Bearer $token" \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "https://api.github.com/user" 2>/dev/null) http_code="${response: -3}" case "$http_code" in 200) config_debug "GitHub token is valid" return 0 ;; 401) config_error "GitHub token is invalid or expired" return 1 ;; 403) config_error "GitHub token lacks required permissions" return 1 ;; *) config_error "GitHub API request failed with HTTP $http_code" return 1 ;; esac } # Get GitHub user information using PAT get_github_user_info() { local token="$1" local timeout="${2:-10}" if [[ -z "$token" ]]; then config_error "No token provided" return 1 fi config_debug "Fetching GitHub user information" local response response=$(curl -s --max-time "$timeout" \ -H "Authorization: Bearer $token" \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "https://api.github.com/user" 2>/dev/null) if [[ $? -eq 0 ]] && [[ -n "$response" ]]; then # Extract key information using simple grep/sed (avoid jq dependency) local login local name local email login=$(echo "$response" | grep -o '"login"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"login"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') name=$(echo "$response" | grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') email=$(echo "$response" | grep -o '"email"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"email"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') echo "login:$login" echo "name:$name" echo "email:$email" return 0 else config_error "Failed to fetch GitHub user information" return 1 fi } # Store GitHub PAT token securely store_github_token() { local token="$1" local token_file="${2:-$GITHUB_TOKEN_FILE}" config_debug "Storing GitHub token to: $token_file" if [[ -z "$token" ]]; then config_error "No token provided for storage" return 1 fi # Validate token format if ! validate_github_token_format "$token"; then config_error "Invalid GitHub token format" return 1 fi # Test token before storing if ! test_github_token "$token"; then config_error "GitHub token validation failed" return 1 fi # Backup existing token file if [[ -f "$token_file" ]]; then backup_file "$token_file" >/dev/null fi # Store token with secure permissions echo "$token" > "$token_file" set_secure_permissions "$token_file" 600 # Also store in environment configuration write_config_value "GITHUB_TOKEN" "$token" config_success "GitHub token stored successfully" return 0 } # Load GitHub PAT token from various sources load_github_token() { config_debug "Loading GitHub token from available sources" local token="" # Priority order: # 1. Environment variable GITHUB_TOKEN # 2. Token file # 3. Configuration file # 4. GitHub auth script # Check environment variable if [[ -n "${GITHUB_TOKEN:-}" ]]; then config_debug "Using GitHub token from environment variable" token="$GITHUB_TOKEN" # Check token file elif [[ -f "$GITHUB_TOKEN_FILE" ]]; then config_debug "Loading GitHub token from file: $GITHUB_TOKEN_FILE" token=$(cat "$GITHUB_TOKEN_FILE" 2>/dev/null | tr -d '\n\r') # Check configuration file elif [[ -f "$ENV_CONFIG_FILE" ]]; then config_debug "Loading GitHub token from config file" token=$(read_config_value "GITHUB_TOKEN") # Try GitHub auth script elif [[ -x "$GITHUB_AUTH_SCRIPT" ]]; then config_debug "Attempting to get token from GitHub auth script" token=$(python3 "$GITHUB_AUTH_SCRIPT" token 2>/dev/null || echo "") fi if [[ -n "$token" ]]; then # Validate token if validate_github_token_format "$token" && test_github_token "$token"; then export GITHUB_TOKEN="$token" config_debug "GitHub token loaded and validated successfully" return 0 else config_warning "Loaded GitHub token is invalid" return 1 fi else config_debug "No GitHub token found" return 1 fi } # Remove GitHub PAT token remove_github_token() { local token_file="${1:-$GITHUB_TOKEN_FILE}" config_info "Removing GitHub token" # Remove token file if [[ -f "$token_file" ]]; then backup_file "$token_file" >/dev/null rm -f "$token_file" config_debug "Token file removed: $token_file" fi # Remove from configuration remove_config_value "GITHUB_TOKEN" # Clear environment variable unset GITHUB_TOKEN config_success "GitHub token removed successfully" return 0 } # [AWS-SECRET-REMOVED]==================================== # MIGRATION AND UPGRADE UTILITIES # [AWS-SECRET-REMOVED]==================================== # Migrate configuration from old format to new format migrate_configuration() { config_info "Checking for configuration migration needs" local migration_needed=false # Check for old configuration files local old_configs=( "$CONFIG_DIR/***REMOVED***.automation" "$CONFIG_DIR/automation.conf" "$CONFIG_DIR/config***REMOVED***" ) for old_config in "${old_configs[@]}"; do if [[ -f "$old_config" ]]; then config_info "Found old configuration file: $old_config" migration_needed=true # Backup old config backup_file "$old_config" >/dev/null # Migrate values if possible if [[ -r "$old_config" ]]; then config_info "Migrating values from $old_config" # Simple migration - source old config and write values to new config while IFS='=' read -r key value; do # Skip comments and empty lines [[ "$key" =~ ^[[:space:]]*# ]] && continue [[ -z "$key" ]] && continue # Clean up key and value key=$(echo "$key" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//') value=$(echo "$value" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//' | sed 's/^["'\'']\(.*\)["'\'']$/\1/') if [[ -n "$key" ]] && [[ -n "$value" ]]; then write_config_value "$key" "$value" config_debug "Migrated: $key=$value" fi done < "$old_config" fi fi done if [[ "$migration_needed" == "true" ]]; then config_success "Configuration migration completed" else config_debug "No migration needed" fi return 0 } # [AWS-SECRET-REMOVED]==================================== # SYSTEM INTEGRATION # [AWS-SECRET-REMOVED]==================================== # Check if systemd service is available and configured check_systemd_service() { config_debug "Checking systemd service configuration" if ! command_exists systemctl; then config_warning "systemd not available on this system" return 1 fi if [[ ! -f "$SERVICE_FILE" ]]; then config_warning "Service file not found: $SERVICE_FILE" return 1 fi # Check if service is installed if systemctl list-unit-files "$SERVICE_NAME.service" >/dev/null 2>&1; then config_debug "Service is installed: $SERVICE_NAME" # Check service status local status status=$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || echo "inactive") config_debug "Service status: $status" return 0 else config_debug "Service is not installed: $SERVICE_NAME" return 1 fi } # Get systemd service status get_service_status() { if ! command_exists systemctl; then echo "systemd_unavailable" return 1 fi local status status=$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || echo "inactive") echo "$status" case "$status" in active) return 0 ;; inactive|failed) return 1 ;; *) return 2 ;; esac } # [AWS-SECRET-REMOVED]==================================== # MAIN CONFIGURATION INTERFACE # [AWS-SECRET-REMOVED]==================================== # Show current configuration status show_config_status() { config_info "ThrillWiki Automation Configuration Status" echo "[AWS-SECRET-REMOVED]======" echo "" # Project information echo "📁 Project Directory: $CONFIG_DIR" echo "🔧 Configuration Version: $AUTOMATION_CONFIG_VERSION" echo "" # Configuration files echo "📄 Configuration Files:" if [[ -f "$ENV_CONFIG_FILE" ]]; then echo " ✅ Environment config: $ENV_CONFIG_FILE" local perms perms=$(stat -c "%a" "$ENV_CONFIG_FILE" 2>/dev/null || stat -f "%A" "$ENV_CONFIG_FILE" 2>/dev/null) echo " Permissions: $perms" else echo " ❌ Environment config: Not found" fi if [[ -f "$ENV_EXAMPLE_FILE" ]]; then echo " ✅ Example config: $ENV_EXAMPLE_FILE" else echo " ❌ Example config: Not found" fi echo "" # GitHub authentication echo "🔐 GitHub Authentication:" if load_github_token >/dev/null 2>&1; then echo " ✅ GitHub token: Available and valid" # Get user info local user_info user_info=$(get_github_user_info "$GITHUB_TOKEN" 2>/dev/null) if [[ -n "$user_info" ]]; then local login login=$(echo "$user_info" | grep "^login:" | cut -d: -f2) if [[ -n "$login" ]]; then echo " Authenticated as: $login" fi fi else echo " ❌ GitHub token: Not available or invalid" fi if [[ -f "$GITHUB_TOKEN_FILE" ]]; then echo " ✅ Token file: $GITHUB_TOKEN_FILE" else echo " ❌ Token file: Not found" fi echo "" # Systemd service echo "⚙️ Systemd Service:" if check_systemd_service; then echo " ✅ Service file: Available" local status status=$(get_service_status) echo " Status: $status" else echo " ❌ Service: Not configured or available" fi echo "" # Backups echo "💾 Backups:" if [[ -d "$CONFIG_BACKUP_DIR" ]]; then local backup_count backup_count=$(find "$CONFIG_BACKUP_DIR" -name "*.backup" 2>/dev/null | wc -l) echo " 📦 Backup directory: $CONFIG_BACKUP_DIR" echo " 📊 Backup files: $backup_count" else echo " ❌ No backup directory found" fi } # Initialize configuration system init_configuration() { config_info "Initializing ThrillWiki automation configuration" # Create necessary directories ensure_directory "$CONFIG_BACKUP_DIR" ensure_directory "$(dirname "$ENV_CONFIG_FILE")" # Run migration if needed migrate_configuration # Create configuration file from example if it doesn't exist if [[ ! -f "$ENV_CONFIG_FILE" ]] && [[ -f "$ENV_EXAMPLE_FILE" ]]; then config_info "Creating configuration file from template" cp "$ENV_EXAMPLE_FILE" "$ENV_CONFIG_FILE" set_secure_permissions "$ENV_CONFIG_FILE" 600 config_success "Configuration file created: $ENV_CONFIG_FILE" fi # Validate configuration validate_config_file config_success "Configuration system initialized" return 0 } # [AWS-SECRET-REMOVED]==================================== # COMMAND LINE INTERFACE # [AWS-SECRET-REMOVED]==================================== # Show help information show_config_help() { echo "ThrillWiki Automation Configuration Library v$AUTOMATION_CONFIG_VERSION" echo "Usage: source automation-config.sh" echo "" echo "Available Functions:" echo " Configuration Management:" echo " read_config_value [file] [default] - Read configuration value" echo " write_config_value [file] - Write configuration value" echo " remove_config_value [file] - Remove configuration value" echo " validate_config_file [file] - Validate configuration file" echo "" echo " GitHub Token Management:" echo " load_github_token - Load GitHub token from sources" echo " store_github_token [file] - Store GitHub token securely" echo " test_github_token - Test GitHub token validity" echo " remove_github_token [file] - Remove GitHub token" echo "" echo " System Status:" echo " show_config_status - Show configuration status" echo " check_systemd_service - Check systemd service status" echo " get_service_status - Get service active status" echo "" echo " Utilities:" echo " init_configuration - Initialize configuration system" echo " migrate_configuration - Migrate old configuration" echo " backup_file [backup_dir] - Backup file with timestamp" echo "" echo "Configuration Files:" echo " $ENV_CONFIG_FILE" echo " $GITHUB_TOKEN_FILE" echo "" } # If script is run directly (not sourced), show help if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then show_config_help exit 0 fi # Export key functions for use by other scripts export -f read_config_value write_config_value remove_config_value validate_config_file export -f load_github_token store_github_token test_github_token remove_github_token export -f show_config_status check_systemd_service get_service_status export -f init_configuration migrate_configuration backup_file export -f config_info config_success config_warning config_error config_debug config_debug "Automation configuration library loaded successfully"