# Configuration Management System ## Overview The configuration management system provides a safe, automated way to merge new configuration keys from version-controlled `.example` files into active configuration files during deployments. This system is essential for white-label installations where each deployment has custom configuration values that must be preserved across updates. ### The Problem It Solves In a white-label Laravel application with multiple installations: - **Active config files** (`config/timebank_cc.php`, `config/timebank-default.php`, `config/themes.php`) contain installation-specific custom values - **Example config files** (`config/*.php.example`) are tracked in git and receive updates with new features - During deployment, new configuration keys must be added **without overwriting existing custom values** - Manual merging is error-prone and doesn't scale across multiple installations ### How It Works The system uses a **deep merge algorithm** that: 1. Recursively compares active configs with their `.example` counterparts 2. Identifies new keys that don't exist in the active config 3. Adds only the new keys while preserving ALL existing custom values 4. Creates automatic timestamped backups before any changes 5. Validates the merged configuration can be loaded successfully **Key Principle**: Existing values are NEVER overwritten. Only new keys are added. ### Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ Deployment Flow │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. deploy.sh runs │ │ 2. Git pulls latest code (includes updated .example files) │ │ 3. Config merge check runs │ │ ├─ Compares: config/file.php vs config/file.php.example │ │ ├─ Detects: New keys in .example │ │ └─ Prompts: "Merge new keys? (y/N)" │ │ 4. If user confirms: │ │ ├─ Creates backup: storage/config-backups/file.php.backup.* │ │ ├─ Deep merges: Adds new keys, preserves existing values │ │ ├─ Validates: Ensures merged config is valid PHP │ │ └─ Logs: Records merge to Laravel log │ │ 5. Deployment continues │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Files Managed The system manages three configuration files: | Config File | Purpose | Custom Values Example | |------------|---------|----------------------| | `config/themes.php` | Theme definitions and color schemes | Custom brand colors, font choices | | `config/timebank-default.php` | Default platform configuration | Transaction limits, validation rules | | `config/timebank_cc.php` | Installation-specific overrides | Platform name, currency, feature flags | Each has a corresponding `.example` file tracked in git: - `config/themes.php.example` - `config/timebank-default.php.example` - `config/timebank_cc.php.example` ## Setup Instructions ### Initial Installation When setting up a new installation, the deployment script automatically creates active config files from examples if they don't exist: ```bash # During first deployment, deploy.sh automatically runs: for config_file in config/themes.php config/timebank-default.php config/timebank_cc.php; do if [ ! -f "$config_file" ] && [ -f "${config_file}.example" ]; then cp "${config_file}.example" "$config_file" fi done ``` ### Customizing Configuration After initial setup, customize your active config files: ```php // config/timebank_cc.php return [ 'platform_name' => 'My TimeBank', // Custom installation name 'currency' => 'hours', // Custom currency name 'wirechat' => [ 'disappearing_messages' => [ 'duration' => 720, // Custom: 12 hours instead of default 6 ], ], 'transactions' => [ 'limits' => [ 'user' => 1000, // Custom: higher limit than default ], ], ]; ``` **Important**: Never edit `.example` files for installation-specific changes. Always edit the active config files. ### Backup System The system automatically manages backups: - **Location**: `storage/config-backups/` - **Format**: `{filename}.backup.{YYYY-MM-DD_HHmmss}` - **Retention**: Last 5 backups per file (older ones auto-deleted) - **Created**: Before every merge operation Example backup files: ``` storage/config-backups/ ├── timebank_cc.php.backup.2026-01-06_143022 ├── timebank_cc.php.backup.2026-01-05_091534 ├── timebank_cc.php.backup.2026-01-03_164411 ├── themes.php.backup.2026-01-06_143022 └── themes.php.backup.2026-01-04_102357 ``` ## Updating Configuration / New Deploys ### Automatic Merge During Deployment When running `deploy.sh`, the system automatically detects configuration updates: ```bash ./deploy.sh ``` **What happens**: 1. **Detection Phase**: ``` ═══════════════════════════════════════════════════════════ CHECKING FOR CONFIGURATION UPDATES ═══════════════════════════════════════════════════════════ New configuration keys available in .example files Review changes with: php artisan config:merge --all --dry-run Would you like to merge new keys now? (y/N) ``` 2. **If you press 'y'**: - Creates automatic backup - Merges new keys - Preserves all existing values - Shows summary of changes 3. **If you press 'N'**: - Deployment continues - You can merge manually later ### Manual Configuration Merge You can merge configurations manually at any time: #### Preview Changes (Dry-Run Mode) **Recommended first step**: Always preview changes before applying: ```bash # Preview all config files php artisan config:merge --all --dry-run # Preview specific config php artisan config:merge timebank_cc --dry-run ``` **Example output**: ``` ───────────────────────────────────────────────────── Config: timebank_cc ───────────────────────────────────────────────────── Found 3 new configuration key(s): + wirechat.notifications.sound_enabled true + wirechat.notifications.desktop_enabled true + footer.sections.2.links.3 [route: static-contact, title: Contact, order: 3, visible: true] ``` #### Apply Changes ```bash # Merge all configs (with confirmation prompts) php artisan config:merge --all # Merge specific config php artisan config:merge timebank_cc php artisan config:merge timebank-default php artisan config:merge themes # Merge without confirmation (automated deployments) php artisan config:merge --all --force ``` **Interactive merge process**: ``` ───────────────────────────────────────────────────── Config: timebank_cc ───────────────────────────────────────────────────── Found 3 new configuration key(s): + wirechat.notifications.sound_enabled true + wirechat.notifications.desktop_enabled true + footer.tagline "Your time is currency" Merge these keys into timebank_cc? (yes/no) [no]: > yes Backup created: storage/config-backups/timebank_cc.php.backup.2026-01-06_143022 ✓ timebank_cc: Successfully merged 3 new keys ``` ### Restoring from Backup If you need to rollback a configuration change: ```bash php artisan config:merge --restore ``` **Interactive restore process**: ``` Available backups: timebank_cc.php 1. 2026-01-06 14:30:22 2. 2026-01-05 09:15:34 3. 2026-01-03 16:44:11 themes.php 4. 2026-01-06 14:30:22 5. 2026-01-04 10:23:57 Enter backup number to restore (or 0 to cancel): > 1 Restore timebank_cc.php from backup? (yes/no) [no]: > yes Current config backed up to: storage/config-backups/timebank_cc.php.backup.2026-01-06_144512 ✓ Successfully restored timebank_cc.php ``` ## Command Reference ### php artisan config:merge Merge new configuration keys from `.example` files into active configs. **Syntax**: ```bash php artisan config:merge [file] [options] ``` **Arguments**: - `file` - Specific config to merge: `themes`, `timebank-default`, or `timebank_cc` (optional) **Options**: - `--all` - Merge all config files - `--dry-run` - Preview changes without applying - `--force` - Skip confirmation prompts - `--restore` - Restore from backup (interactive) **Examples**: ```bash # Preview all changes php artisan config:merge --all --dry-run # Merge all with confirmation php artisan config:merge --all # Merge specific file php artisan config:merge timebank_cc # Automated merge (no prompts) php artisan config:merge --all --force # Restore from backup php artisan config:merge --restore ``` **Exit Codes**: - `0` - Success (changes applied or no changes needed) - `1` - Error (invalid file, backup failed, etc.) ## Deep Merge Algorithm ### How It Works The deep merge algorithm recursively processes configuration arrays: ```php function deepMergeNewKeys(current, example): result = current // Start with existing config for each key, value in example: if key does NOT exist in current: // NEW KEY - Add it result[key] = value else if value is array AND current[key] is array: // Both are arrays - RECURSE result[key] = deepMergeNewKeys(current[key], value) else: // Key exists and not both arrays - PRESERVE current value // Do nothing - keep current[key] unchanged return result ``` ### Merge Examples #### Example 1: Adding New Top-Level Keys **Current config**: ```php [ 'platform_name' => 'My TimeBank', // Custom value 'currency' => 'hours', // Custom value ] ``` **Example config** (from git update): ```php [ 'platform_name' => 'TimeBank CC', // Default value 'currency' => 'time', // Default value 'timezone' => 'UTC', // NEW KEY ] ``` **Result after merge**: ```php [ 'platform_name' => 'My TimeBank', // PRESERVED - custom value kept 'currency' => 'hours', // PRESERVED - custom value kept 'timezone' => 'UTC', // ADDED - new key ] ``` #### Example 2: Adding Nested Keys **Current config**: ```php [ 'wirechat' => [ 'disappearing_messages' => [ 'duration' => 720, // Custom: 12 hours ], ], ] ``` **Example config** (from git update): ```php [ 'wirechat' => [ 'disappearing_messages' => [ 'duration' => 360, // Default: 6 hours 'cleanup_schedule' => 'hourly', // NEW KEY ], 'notifications' => [ // NEW NESTED SECTION 'sound_enabled' => true, 'desktop_enabled' => true, ], ], ] ``` **Result after merge**: ```php [ 'wirechat' => [ 'disappearing_messages' => [ 'duration' => 720, // PRESERVED - custom value kept 'cleanup_schedule' => 'hourly', // ADDED - new key ], 'notifications' => [ // ADDED - entire new section 'sound_enabled' => true, 'desktop_enabled' => true, ], ], ] ``` #### Example 3: Array Values **Current config**: ```php [ 'allowed_types' => ['user', 'organization'], // Custom list ] ``` **Example config** (from git update): ```php [ 'allowed_types' => ['user', 'organization', 'bank'], // Updated list ] ``` **Result after merge**: ```php [ 'allowed_types' => ['user', 'organization'], // PRESERVED - arrays not merged ] ``` **Note**: Array values are treated as complete values, not merged element-by-element. If you need the new array values, update them manually after reviewing the diff. ## Best Practices ### 1. Always Preview First Before applying configuration merges, especially in production: ```bash # See exactly what will change php artisan config:merge --all --dry-run ``` ### 2. Review Changes in Detail When new keys are detected: 1. Review what each new key does (check git commit messages) 2. Verify default values are appropriate for your installation 3. Adjust values after merge if needed ### 3. Test After Merging After merging configuration changes: ```bash # Verify config can be loaded php artisan config:cache # Clear and rebuild cache php artisan optimize:clear php artisan optimize # Test critical functionality php artisan test ``` ### 4. Keep Backups The system keeps 5 backups automatically, but for major updates: ```bash # Create manual backup before major changes cp config/timebank_cc.php config/timebank_cc.php.manual-backup-$(date +%Y%m%d) ``` ### 5. Document Custom Changes Add comments to your active config files explaining why values differ from defaults: ```php return [ 'transactions' => [ 'limits' => [ // Custom: Increased from 500 to 1000 for high-volume community 'user' => 1000, ], ], ]; ``` ### 6. Staged Deployments For multi-server deployments: 1. Test config merge on staging server first 2. Verify application functionality 3. Then deploy to production with `--force` flag ```bash # Staging (with prompts) ./deploy.sh # Production (automated) ./deploy.sh --force ``` ## Troubleshooting ### Config Merge Shows No Changes But I Know There Are Updates **Cause**: The active config may already have the keys (possibly added manually). **Solution**: ```bash # Compare files manually diff config/timebank_cc.php config/timebank_cc.php.example ``` ### Merge Failed - Config Invalid **Symptom**: Error message "Merged config is invalid" **Cause**: Syntax error in merged configuration. **Solution**: Automatic rollback occurs. Check Laravel log for details: ```bash tail -50 storage/logs/laravel.log | grep "config:merge" ``` ### Backup Directory Not Writable **Symptom**: "Failed to create backup" **Solution**: Ensure storage directory is writable: ```bash chmod -R 775 storage chown -R www-data:www-data storage # Adjust user/group as needed ``` ### Need to Restore But Backups Are Missing **Cause**: Backups older than 5 merges were auto-deleted. **Solution**: - If config is in git-ignored files, check git stash - Restore from server backups - Manually recreate configuration **Prevention**: Create manual backups before major updates. ### Config Merge Hangs During Deployment **Cause**: Waiting for user input in automated environment. **Solution**: Use `--force` flag in automated deployments: ```bash # In deploy.sh for automated servers php artisan config:merge --all --force ``` ## Integration with CI/CD ### Automated Deployments For CI/CD pipelines, use non-interactive mode: ```bash # In deployment script php artisan config:merge --all --force || { echo "Config merge failed - check logs" exit 1 } ``` ### Pre-Deployment Validation Check for config updates before deployment: ```bash # In CI pipeline if php artisan config:merge --all --dry-run | grep -q "Found [0-9]"; then echo "⚠️ Configuration updates detected - review required" php artisan config:merge --all --dry-run fi ``` ### Post-Deployment Verification Verify config after deployment: ```bash # Ensure config is valid php artisan config:cache || { echo "Config cache failed - configuration may be invalid" exit 1 } # Run config-dependent tests php artisan test --filter ConfigTest ``` ## Logging and Auditing All configuration merges are logged to Laravel's log system: ```bash # View recent config merges tail -100 storage/logs/laravel.log | grep "Config merged" ``` **Log entry example**: ```json { "level": "info", "message": "Config merged", "context": { "file": "timebank_cc", "path": "config/timebank_cc.php", "new_keys": [ "wirechat.notifications.sound_enabled", "wirechat.notifications.desktop_enabled", "footer.tagline" ], "new_key_count": 3, "backup": "storage/config-backups/timebank_cc.php.backup.2026-01-06_143022" }, "timestamp": "2026-01-06 14:30:22" } ``` This provides a complete audit trail of all configuration changes.