17 KiB
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:
- Recursively compares active configs with their
.examplecounterparts - Identifies new keys that don't exist in the active config
- Adds only the new keys while preserving ALL existing custom values
- Creates automatic timestamped backups before any changes
- 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.exampleconfig/timebank-default.php.exampleconfig/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:
# 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:
// 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:
./deploy.sh
What happens:
-
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) -
If you press 'y':
- Creates automatic backup
- Merges new keys
- Preserves all existing values
- Shows summary of changes
-
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:
# 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
# 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:
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:
php artisan config:merge [file] [options]
Arguments:
file- Specific config to merge:themes,timebank-default, ortimebank_cc(optional)
Options:
--all- Merge all config files--dry-run- Preview changes without applying--force- Skip confirmation prompts--restore- Restore from backup (interactive)
Examples:
# 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:
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:
[
'platform_name' => 'My TimeBank', // Custom value
'currency' => 'hours', // Custom value
]
Example config (from git update):
[
'platform_name' => 'TimeBank CC', // Default value
'currency' => 'time', // Default value
'timezone' => 'UTC', // NEW KEY
]
Result after merge:
[
'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:
[
'wirechat' => [
'disappearing_messages' => [
'duration' => 720, // Custom: 12 hours
],
],
]
Example config (from git update):
[
'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:
[
'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:
[
'allowed_types' => ['user', 'organization'], // Custom list
]
Example config (from git update):
[
'allowed_types' => ['user', 'organization', 'bank'], // Updated list
]
Result after merge:
[
'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:
# See exactly what will change
php artisan config:merge --all --dry-run
2. Review Changes in Detail
When new keys are detected:
- Review what each new key does (check git commit messages)
- Verify default values are appropriate for your installation
- Adjust values after merge if needed
3. Test After Merging
After merging configuration changes:
# 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:
# 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:
return [
'transactions' => [
'limits' => [
// Custom: Increased from 500 to 1000 for high-volume community
'user' => 1000,
],
],
];
6. Staged Deployments
For multi-server deployments:
- Test config merge on staging server first
- Verify application functionality
- Then deploy to production with
--forceflag
# 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:
# 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:
tail -50 storage/logs/laravel.log | grep "config:merge"
Backup Directory Not Writable
Symptom: "Failed to create backup"
Solution: Ensure storage directory is writable:
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:
# 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:
# 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:
# 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:
# 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:
# View recent config merges
tail -100 storage/logs/laravel.log | grep "Config merged"
Log entry example:
{
"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.