CresignSys Learn — Lesson 093

Written by

in

Build the CHP Configuration Reconciliation Engine

The Health Engine answers:

Is the website working?

The Reconciliation Engine answers:

Is the website configured the way CHP expects?

These are different questions.

A website can be healthy but incorrectly configured.

Health:
HEALTHY

Configuration:
DRIFTED

For example:

Desired PHP:
8.3

Actual PHP:
8.2

The website may still work, but the configuration is no longer what CHP expects.


1. Desired State vs Actual State

The central model is:

              DESIRED STATE
                    │
                    ▼
              RECONCILIATION
                    ▲
                    │
              ACTUAL STATE
                    │
                    ▼
                  DIFF

Result:

MATCH
DRIFT
UNKNOWN
CONFLICT

2. What Is Desired State?

Desired state is what CHP believes the site should look like.

Example:

{
  "domain": "example.com",
  "web_root": "/storage/websites/example.com/public",
  "php_version": "8.3",
  "php_fpm_socket": "/run/php/php8.3-fpm.sock",
  "ssl_enabled": true,
  "backup_enabled": true,
  "health_monitoring": true
}

3. What Is Actual State?

Actual state is what CHP discovers from the server.

Example:

{
  "domain": "example.com",
  "web_root": "/storage/websites/example.com/public",
  "php_version": "8.2",
  "php_fpm_socket": "/run/php/php8.2-fpm.sock",
  "ssl_enabled": true,
  "backup_enabled": true,
  "health_monitoring": true
}

4. Reconciliation

Compare:

DESIRED                    ACTUAL
--------                   -------
PHP 8.3                    PHP 8.2
SSL enabled                SSL enabled
Backup enabled             Backup enabled
Health enabled             Health enabled

Result:

PHP:
DRIFT

SSL:
MATCH

Backup:
MATCH

Health:
MATCH

5. Reconciliation Must Not Automatically Repair

This is critical.

The reconciliation engine should initially do:

DISCOVER
   ↓
COMPARE
   ↓
REPORT

not:

DISCOVER
   ↓
COMPARE
   ↓
CHANGE PRODUCTION

The repair system remains a separate controlled process.


6. Architecture

                  SITE
                   │
          ┌────────┴────────┐
          ▼                 ▼
    DESIRED STATE       ACTUAL STATE
          │                 │
          └────────┬────────┘
                   ▼
             RECONCILIATION
                   │
                   ▼
                  DIFF
                   │
        ┌──────────┼──────────┐
        ▼          ▼          ▼
      MATCH       DRIFT     UNKNOWN
                   │
                   ▼
              REPAIR PLAN

7. Configuration Domains

Reconciliation should be divided into domains:

DOMAIN
WEB
PHP
DATABASE
SSL
DNS
FILESYSTEM
WORDPRESS
BACKUP
HEALTH

8. Why Divide Configuration?

Suppose:

PHP:
DRIFT

while:

SSL:
MATCH

The system can generate a precise repair plan instead of treating the whole site as broken.


9. Desired State Table

Create:

CREATE TABLE desired_config (
    id INTEGER PRIMARY KEY AUTOINCREMENT,

    site_id INTEGER NOT NULL,
    config_domain TEXT NOT NULL,

    config_version INTEGER NOT NULL DEFAULT 1,

    desired_state TEXT NOT NULL,

    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,

    FOREIGN KEY (site_id)
        REFERENCES sites(id)
        ON DELETE CASCADE
);

10. Example

For:

example.com

you could have:

WEB
PHP
SSL
DATABASE
BACKUP
HEALTH

as separate configuration records.


11. Why config_version?

Suppose:

version 1:
PHP 8.2

then:

version 2:
PHP 8.3

CHP can determine:

desired configuration changed

rather than confusing it with external drift.


12. Configuration Fingerprint

Create a normalized representation:

{
  "php_version": "8.3",
  "ssl_enabled": true,
  "backup_enabled": true
}

Then calculate a hash.

Example:

desired fingerprint:
abc123

Actual:

actual fingerprint:
def456

If:

abc123 == def456

the states match.


13. Don’t Compare Raw JSON Text

These two are logically identical:

{
  "php": "8.3",
  "ssl": true
}

and:

{
  "ssl": true,
  "php": "8.3"
}

String comparison would incorrectly report drift.

Normalize the data first.


14. Normalization

Before comparison:

RAW
 ↓
NORMALIZE
 ↓
SORT
 ↓
STANDARDIZE
 ↓
COMPARE

For example:

PHP:
"8.3 "

should become:

"8.3"

before comparison.


15. Actual State Collector

Create:

sudo nano /etc/cresignsys/lib/state-collector.sh

Functions:

collect_domain_state
collect_web_state
collect_php_state
collect_database_state
collect_ssl_state
collect_dns_state
collect_filesystem_state
collect_wordpress_state

16. Domain State

Collect:

primary domain
aliases
www domain
document root

Example:

{
  "domain": "example.com",
  "aliases": [
    "www.example.com"
  ],
  "web_root": "/storage/websites/example.com/public"
}

17. Web State

Collect:

Nginx site configuration
enabled/disabled
configuration test
server blocks
proxy configuration
PHP handler

Example:

{
  "server": "nginx",
  "enabled": true,
  "config_test": true
}

18. PHP State

Collect:

PHP version
PHP-FPM service
PHP-FPM socket
configured pool

Example:

{
  "version": "8.3",
  "fpm_service": "php8.3-fpm",
  "socket": "/run/php/php8.3-fpm.sock"
}

19. Database State

Collect:

engine
database name
host
port
connectivity

Do not expose:

password

in the actual-state object.


20. SSL State

Collect:

enabled
certificate expiry
issuer
subject
SAN
certificate fingerprint

Example:

{
  "enabled": true,
  "expires_at": "2026-09-20T00:00:00",
  "issuer": "Let's Encrypt",
  "fingerprint": "..."
}

21. DNS State

Collect:

A
AAAA
CNAME

where applicable.

Example:

{
  "A": [
    "203.0.113.10"
  ]
}

Don’t automatically classify a DNS difference as an error.

It could be intentional.


22. Filesystem State

Collect:

web root exists
owner
group
permissions
storage filesystem
disk usage
inode usage

Example:

{
  "exists": true,
  "owner": "www-data",
  "group": "www-data",
  "permissions": "0755"
}

23. WordPress State

For WordPress:

core version
plugin list
theme
site URL
database connection

Example:

{
  "installed": true,
  "core_version": "7.0.4"
}

24. WordPress Plugin State

A desired configuration could specify:

{
  "required_plugins": [
    "plugin-a",
    "plugin-b"
  ]
}

Actual state:

{
  "installed_plugins": [
    "plugin-a",
    "plugin-c"
  ]
}

Reconciliation detects:

plugin-b:
MISSING

plugin-c:
UNEXPECTED

But this should be treated carefully.

An unexpected plugin isn’t automatically something CHP should delete.


25. Configuration Ownership

This leads to an important concept:

CHP should only reconcile configuration that it owns.

For example:

CHP-managed:
Nginx virtual host
PHP-FPM mapping
backup schedule
health policy

But perhaps:

customer-managed:
WordPress plugins
WordPress themes
application content

unless the hosting plan explicitly enables CHP management.


26. Managed Fields

Add ownership:

{
  "php_version": {
    "value": "8.3",
    "managed": true
  },

  "plugins": {
    "value": [],
    "managed": false
  }
}

This prevents CHP from fighting customer changes.


27. Desired State Should Be Explicit

Don’t infer:

"Everything I discover is what the site should be."

Instead:

desired:
explicitly configured

and:

actual:
discovered

28. Unknown Desired State

If CHP doesn’t know:

desired PHP version

then:

actual PHP:
8.3

should produce:

UNKNOWN

not:

MATCH

and not:

DRIFT

29. Unknown Actual State

If CHP cannot determine:

PHP-FPM socket

then:

actual:
UNKNOWN

The result becomes:

UNKNOWN

rather than assuming:

DRIFT

30. Conflict

A conflict is different from ordinary drift.

Example:

Desired:
PHP 8.3

Actual:
PHP 8.2

Another active CHP job:
upgrade PHP to 8.4

Now CHP has conflicting desired information.

Result:

CONFLICT

rather than simply:

DRIFT

31. Reconciliation Result Table

Create:

CREATE TABLE reconciliation_results (
    id INTEGER PRIMARY KEY AUTOINCREMENT,

    site_id INTEGER NOT NULL,

    config_domain TEXT NOT NULL,

    status TEXT NOT NULL,

    desired_state TEXT,
    actual_state TEXT,

    diff TEXT,

    desired_fingerprint TEXT,
    actual_fingerprint TEXT,

    checked_at TEXT NOT NULL,

    FOREIGN KEY (site_id)
        REFERENCES sites(id)
        ON DELETE CASCADE
);

32. Result Status

Use:

MATCH
DRIFT
UNKNOWN
CONFLICT
ERROR

33. Diff Object

Example:

{
  "php_version": {
    "desired": "8.3",
    "actual": "8.2",
    "change": "MODIFY"
  }
}

This can directly feed the repair planner.


34. Multiple Drift Items

Example:

{
  "php_version": {
    "desired": "8.3",
    "actual": "8.2"
  },
  "web_root": {
    "desired": "/storage/websites/example.com/public",
    "actual": "/storage/websites/example.com/public_html"
  }
}

The repair planner can then determine whether these changes are related.


35. Reconciliation Command

Create:

sudo nano /usr/local/bin/hosting-reconcile

Usage:

sudo hosting-reconcile example.com

36. Example Output

CresignSys Reconciliation
=========================

Site:
example.com

DOMAIN:
MATCH

WEB:
MATCH

PHP:
DRIFT

  Desired:
  8.3

  Actual:
  8.2

DATABASE:
MATCH

SSL:
MATCH

BACKUP:
MATCH

HEALTH:
MATCH

Overall:
DRIFT

37. All Sites

sudo hosting-reconcile --all

Output:

SITE                    RESULT
--------------------------------
example.com             MATCH
shop.example.com        DRIFT
blog.example.com        MATCH
client.example.com      UNKNOWN

38. Reconciliation Scheduler

The reconciliation engine should use the central job queue.

Flow:

scheduler
   ↓
RECONCILE job
   ↓
worker
   ↓
collect actual state
   ↓
load desired state
   ↓
compare
   ↓
store result
   ↓
emit event

39. Reconciliation Frequency

Example:

Nginx:
15 minutes

PHP:
15 minutes

SSL:
1 hour

DNS:
30 minutes

Backup policy:
1 hour

Again, these are examples.

Configuration that changes rarely doesn’t need minute-by-minute reconciliation.


40. Reconciliation Event

If:

MATCH

and remains:

MATCH

don’t generate a new event every cycle.

Only emit meaningful transitions.

Example:

MATCH
 ↓
DRIFT

generates:

DRIFT_DETECTED

Then:

DRIFT
 ↓
MATCH

generates:

DRIFT_RESOLVED

41. Event Metadata

Example:

{
  "domain": "PHP",
  "field": "php_version",
  "desired": "8.3",
  "actual": "8.2"
}

This is much better than:

"PHP mismatch"

42. Drift Fingerprint

Create:

DRIFT:site7:PHP:php_version

or a hash.

This allows the alert system to deduplicate:

DRIFT_DETECTED
DRIFT_DETECTED
DRIFT_DETECTED

into one alert.


43. Drift Alert

The Alert Engine from Lesson 091 can now process:

DRIFT_DETECTED

Example:

WARNING

Configuration drift detected on example.com.

PHP version:
Expected 8.3
Actual 8.2

44. Drift Is Not Always an Error

Suppose customer intentionally changed:

PHP:
8.3 → 8.2

CHP should detect:

DRIFT

but shouldn’t necessarily repair it.

Instead:

DRIFT
+
customer-managed field

could produce:

INFO

or no alert.


45. Managed vs Unmanaged Drift

This is one of the most important CHP concepts.

Managed drift

CHP owns field

Example:

Nginx config

→ alert/repair candidate.

Unmanaged drift

Customer owns field

Example:

WordPress plugin

→ report only.


46. Configuration Ownership Table

Create:

CREATE TABLE config_ownership (
    id INTEGER PRIMARY KEY AUTOINCREMENT,

    site_id INTEGER NOT NULL,
    config_domain TEXT NOT NULL,
    config_key TEXT NOT NULL,

    management_mode TEXT NOT NULL,

    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,

    FOREIGN KEY (site_id)
        REFERENCES sites(id)
        ON DELETE CASCADE
);

Modes:

MANAGED
UNMANAGED
READ_ONLY

47. Example

example.com

Nginx:
MANAGED

PHP:
MANAGED

SSL:
MANAGED

WordPress plugins:
UNMANAGED

WordPress content:
UNMANAGED

48. Why This Is Important for Reseller Hosting

Different customers want different levels of management.

Customer A:

hosting only

Customer B:

managed WordPress

Customer C:

fully managed website

CHP can use ownership policies to support all three.


49. Desired State Sources

Desired configuration can come from:

hosting plan
site settings
customer request
administrator configuration
approved repair plan

But there should be one canonical desired state.


50. Desired State Precedence

Example:

PLAN DEFAULT
      ↓
SITE OVERRIDE
      ↓
APPROVED CHANGE
      ↓
CURRENT DESIRED STATE

This prevents conflicting configuration sources.


51. Don’t Let the Repair Plan Become Desired State Automatically

Suppose a repair plan proposes:

PHP 8.3 → 8.4

The plan should not change the desired state merely because it was created.

Only after explicit approval/execution should the desired state be updated according to the defined policy.


52. Desired State Versioning

Every desired configuration change should create a new version:

version 1:
PHP 8.2

version 2:
PHP 8.3

version 3:
PHP 8.4

This gives CHP configuration history.


53. Configuration History Table

You can eventually add:

CREATE TABLE desired_config_history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,

    site_id INTEGER NOT NULL,
    config_version INTEGER NOT NULL,

    config_state TEXT NOT NULL,

    actor_id INTEGER,
    reason TEXT,

    created_at TEXT NOT NULL
);

54. Example

Version 1
---------
PHP 8.2
Created by:
SYSTEM

Version 2
---------
PHP 8.3
Created by:
ADMIN
Reason:
Upgrade PHP

This gives a clean audit trail.


55. Reconciliation Does Not Mean Enforcement

Think of it as:

RECONCILIATION
=
measurement + comparison

while:

REPAIR
=
controlled enforcement

Keeping them separate is safer.


56. Reconciliation → Repair

Once drift is detected:

DRIFT
 ↓
DIFF
 ↓
REPAIR PLAN

Example:

Plan:
Change PHP 8.2 → 8.3

Then:

PLAN
 ↓
VALIDATE
 ↓
APPROVE
 ↓
JOB
 ↓
BACKUP
 ↓
REPAIR

57. Never Create a Repair Plan From Raw Text

Bad:

"PHP is wrong"

Good:

{
  "operation": "SET_PHP_VERSION",
  "desired": "8.3",
  "actual": "8.2"
}

The repair engine can validate structured operations.


58. Reconciliation Safety

Before creating a repair plan, confirm:

desired state is current
actual state is current
no conflicting job
no active restore
no maintenance lock

59. Reconciliation Race Condition

Example:

18:00
reconciliation sees PHP 8.2

18:01
admin manually changes PHP to 8.3

18:02
old repair plan says:
change 8.2 → 8.3

That plan is now stale.

Therefore the repair planner must re-check actual state before execution.


60. Desired/Actual Version Fingerprint

A repair plan should record:

desired fingerprint:
ABC123

actual fingerprint:
XYZ789

At execution:

current actual fingerprint:
NEW456

If:

XYZ789 != NEW456

the plan is stale.

Stop.


61. Stale Plan

Result:

PLAN_STALE

not:

repair anyway

This prevents CHP from applying outdated assumptions.


62. Reconciliation Status

The site can now have:

CONFIGURATION:
MATCH
DRIFT
UNKNOWN
CONFLICT

and the dashboard can show it separately from:

HEALTH:
HEALTHY

63. Example Dashboard

example.com

Health
------
HEALTHY
98/100

Configuration
-------------
DRIFT

PHP:
Expected 8.3
Actual 8.2

Backup
------
HEALTHY

Security
--------
HEALTHY

Alerts
------
1 WARNING

This is a very useful hosting control-plane view.


64. Reconciliation Summary

For all sites:

SITE                    HEALTH      CONFIG
------------------------------------------------
example.com             HEALTHY     MATCH
shop.example.com        HEALTHY     DRIFT
blog.example.com        WARNING     MATCH
client.example.com      DOWN        UNKNOWN

65. Reconciliation Metrics

Eventually track:

sites matching desired state
sites with drift
sites unknown
sites with conflicts

Example:

Managed Sites:
120

MATCH:
108

DRIFT:
9

UNKNOWN:
2

CONFLICT:
1

66. Configuration Compliance

This becomes useful for hosting plans.

Example:

Configuration compliance:
90%

But again, don’t hide important individual drift behind a single percentage.


67. Event Flow

The complete configuration lifecycle is now:

ACTUAL STATE
      │
      ▼
RECONCILIATION
      │
      ▼
COMPARE
      │
 ┌────┼──────────┐
 ▼    ▼          ▼
MATCH DRIFT    UNKNOWN
       │
       ▼
   EVENT
       │
       ▼
    ALERT
       │
       ▼
REPAIR PLAN
       │
       ▼
APPROVAL
       │
       ▼
JOB
       │
       ▼
REPAIR
       │
       ▼
RECONCILIATION
       │
       ▼
MATCH

This is the core control-loop architecture.


68. The CHP Control Loop

CHP is now moving toward a true reconciliation-based control plane:

           DESIRED
              │
              ▼
           OBSERVE
              │
              ▼
           COMPARE
              │
              ▼
            DRIFT
              │
              ▼
             PLAN
              │
              ▼
          APPROVAL
              │
              ▼
            BACKUP
              │
              ▼
            APPLY
              │
              ▼
           VERIFY
              │
              ▼
          RECONCILE
              │
          ┌───┴───┐
          ▼       ▼
        MATCH    DRIFT
          │       │
          │       └──→ investigate
          ▼
       COMPLETE

69. Lesson 093 — Core Principle

The most important architectural rule is:

Observe first. Change later.

Reconciliation should never assume that a difference automatically means something is broken.

A difference can be:

intentional
unintentional
customer-managed
CHP-managed
temporary
unknown
conflicting

Therefore CHP should first establish:

WHAT IS DESIRED?
WHAT IS ACTUAL?
WHO OWNS THE FIELD?
WHAT EXACTLY IS DIFFERENT?

Only then should the Repair Engine consider changing the site.


Next Lesson — 094

Build the CHP Desired-State & Configuration Management System

The reconciliation engine can now detect:

DESIRED ≠ ACTUAL

But we need a reliable source for:

What exactly is the desired configuration?

Lesson 094 will build the configuration management layer:

HOSTING PLAN
      │
      ▼
SITE TEMPLATE
      │
      ▼
SITE OVERRIDES
      │
      ▼
DESIRED STATE
      │
      ▼
RECONCILIATION

It will define:

site profiles
PHP policies
Nginx policies
SSL policies
backup policies
health policies
resource limits
WordPress management mode
customer overrides
configuration versioning

and establish which settings are:

DEFAULT
INHERITED
OVERRIDDEN
LOCKED
MANAGED
CUSTOMER-MANAGED

That will provide the missing foundation for generating safe, deterministic Repair Plans.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *