Files
timebank-cc-public/references/CONFIG_MANAGEMENT.md
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

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:

  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:

# 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:

  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:

# 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, 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:

# 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:

  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:

# 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:

  1. Test config merge on staging server first
  2. Verify application functionality
  3. Then deploy to production with --force flag
# 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.