Blog

  • CresignSys Learn — Lesson 084

    Build the CHP Repair-Plan Engine

    We now have:

    hosting-db-import
    hosting-reconcile
    hosting-health
    hosting-site-status

    The next layer is the write-control system.

    The first rule is:

    Detection must never directly cause a server modification.

    Instead:

    DRIFT
      ↓
    ANALYZE
      ↓
    REPAIR PLAN
      ↓
    VALIDATE
      ↓
    APPROVE
      ↓
    APPLY
      ↓
    VERIFY

    1. New Command

    Create:

    sudo nano /usr/local/bin/hosting-repair-plan

    Usage:

    sudo hosting-repair-plan example.com

    Optional:

    sudo hosting-repair-plan example.com --json
    sudo hosting-repair-plan example.com --verbose

    This command must be read-only.


    2. Why a Repair Plan?

    Suppose reconciliation reports:

    PHP:
    DRIFT
    
    Expected:
    8.3
    
    Actual:
    8.2

    Do not immediately execute:

    systemctl restart php8.3-fpm

    There could be several explanations.

    Instead generate:

    Repair Plan
    -----------
    Problem:
    PHP version drift
    
    Expected:
    8.3
    
    Actual:
    8.2
    
    Possible action:
    Change site PHP-FPM target from 8.2 to 8.3

    3. Repair Architecture

                     RECONCILIATION
                           │
                           ▼
                         DRIFT
                           │
                           ▼
                   REPAIR ANALYZER
                           │
                 ┌─────────┼─────────┐
                 ▼         ▼         ▼
               PHP       Nginx      SSL
                 │         │         │
                 └─────────┼─────────┘
                           ▼
                     REPAIR PLAN
                           │
                           ▼
                      VALIDATION
                           │
                           ▼
                       APPROVAL
                           │
                           ▼
                        APPLY
                           │
                           ▼
                      VERIFICATION

    4. Repair Plan Is Not Repair

    These are separate commands:

    hosting-repair-plan
            ↓
    READ ONLY

    and later:

    hosting-repair
            ↓
    WRITE

    This separation should remain permanent.


    5. Repair Plan Structure

    Every repair plan should contain:

    PLAN ID
    SITE
    PROBLEM
    CURRENT STATE
    EXPECTED STATE
    PROPOSED ACTION
    FILES AFFECTED
    SERVICES AFFECTED
    RISK
    BACKUP REQUIREMENT
    ROLLBACK METHOD
    VALIDATION

    6. Example

    CresignSys Repair Plan
    ======================
    
    Plan:
    RP-000021
    
    Site:
    example.com
    
    Problem:
    PHP version drift
    
    Current:
    PHP 8.2
    
    Expected:
    PHP 8.3
    
    Proposed Action:
    Change PHP-FPM target to 8.3
    
    Files:
    1 Nginx configuration
    
    Services:
    Nginx
    
    Backup:
    Required
    
    Rollback:
    Restore previous Nginx configuration
    
    Risk:
    MEDIUM
    
    Validation:
    nginx -t
    HTTP health check
    PHP-FPM check
    
    No changes made.

    7. Repair Plan Database

    Create a new table:

    CREATE TABLE repair_plans (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER NOT NULL,
        plan_code TEXT NOT NULL UNIQUE,
        repair_type TEXT NOT NULL,
        status TEXT NOT NULL,
        risk_level TEXT NOT NULL,
        description TEXT,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
    
        FOREIGN KEY (site_id)
            REFERENCES sites(id)
            ON DELETE CASCADE
    );

    8. Plan Status

    Use:

    DRAFT
    VALID
    APPROVED
    APPLYING
    APPLIED
    FAILED
    CANCELLED
    EXPIRED

    Initial state:

    DRAFT

    After validation:

    VALID

    Only explicit approval moves it to:

    APPROVED

    9. Repair Type

    Examples:

    PHP_VERSION
    NGINX_CONFIGURATION
    SSL
    DNS
    PERMISSIONS
    WORDPRESS
    DATABASE
    STORAGE
    SERVICE

    For example:

    repair_type=PHP_VERSION

    10. Risk Levels

    Start with:

    LOW
    MEDIUM
    HIGH
    CRITICAL

    Example:

    PHP configuration:
    MEDIUM
    
    File ownership:
    MEDIUM
    
    SSL renewal:
    LOW/MEDIUM
    
    DNS:
    HIGH
    
    Database repair:
    HIGH
    
    WordPress database modification:
    HIGH

    The exact risk policy should be configurable later.


    11. Never Automatically Repair HIGH-Risk Operations

    For the first version:

    LOW:
    possible automatic execution later
    
    MEDIUM:
    explicit approval
    
    HIGH:
    explicit approval + stronger validation
    
    CRITICAL:
    manual operator intervention

    Do not implement automatic critical repairs.


    12. Repair Plan Items

    A plan can contain multiple actions.

    Create:

    CREATE TABLE repair_plan_items (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        plan_id INTEGER NOT NULL,
        sequence_no INTEGER NOT NULL,
        action_type TEXT NOT NULL,
        target TEXT,
        old_value TEXT,
        new_value TEXT,
        risk_level TEXT NOT NULL,
        status TEXT NOT NULL,
        FOREIGN KEY (plan_id)
            REFERENCES repair_plans(id)
            ON DELETE CASCADE
    );

    13. Example Plan

    PLAN RP-000021
    
    Item 1
    ------
    Action:
    BACKUP_CONFIG
    
    Target:
    /etc/nginx/sites-enabled/example.com.conf
    
    Item 2
    ------
    Action:
    UPDATE_NGINX_PHP_TARGET
    
    Old:
    php8.2-fpm.sock
    
    New:
    php8.3-fpm.sock
    
    Item 3
    ------
    Action:
    NGINX_CONFIG_TEST
    
    Item 4
    ------
    Action:
    NGINX_RELOAD
    
    Item 5
    ------
    Action:
    HEALTH_CHECK

    14. Why Sequence Numbers?

    Repairs must happen in a controlled order.

    Bad:

    reload Nginx
     ↓
    create backup

    Correct:

    backup
     ↓
    modify
     ↓
    validate
     ↓
    reload
     ↓
    verify

    Therefore:

    sequence_no

    is important.


    15. Action Types

    Define a controlled vocabulary.

    For example:

    BACKUP_FILE
    WRITE_FILE
    REPLACE_TEXT
    CREATE_DIRECTORY
    SET_OWNER
    SET_PERMISSION
    RELOAD_NGINX
    RESTART_SERVICE
    RUN_HEALTH_CHECK
    VERIFY

    Do not allow arbitrary shell commands inside a repair plan.


    16. Never Store Arbitrary Shell Commands

    Avoid:

    command:
    rm -rf /something

    inside the database.

    Instead use structured actions:

    action_type:
    BACKUP_FILE
    
    target:
    /etc/nginx/sites-enabled/example.com.conf

    The repair engine decides what that action means.

    This prevents the repair database from becoming an arbitrary command-execution system.


    17. PHP Repair Example

    Suppose:

    Expected:
    8.3
    
    Actual:
    8.2

    The analyzer checks:

    php8.3-fpm installed?
    php8.3-fpm running?
    socket exists?
    Nginx configuration?

    Only if the required components exist should it propose:

    UPDATE_NGINX_PHP_TARGET

    18. Don’t Install Missing PHP Automatically

    Suppose:

    Expected:
    8.3
    
    Actual:
    8.2
    
    php8.3-fpm:
    NOT INSTALLED

    Do not generate:

    apt install php8.3-fpm

    as an automatic repair.

    Instead:

    Repair:
    BLOCKED
    
    Reason:
    Required PHP-FPM version is not installed.
    
    Manual prerequisite:
    Install and configure PHP 8.3-FPM.

    19. Repair Prerequisites

    A repair plan should distinguish:

    READY

    from:

    BLOCKED

    Example:

    PHP 8.3-FPM:
    AVAILABLE
    
    PHP socket:
    AVAILABLE
    
    Nginx:
    VALID
    
    Repair:
    READY

    20. Example Blocked Plan

    Repair Plan
    ===========
    
    Problem:
    PHP version drift
    
    Expected:
    8.3
    
    Actual:
    8.2
    
    PHP 8.3-FPM:
    NOT AVAILABLE
    
    Status:
    BLOCKED
    
    No changes proposed.

    This is much safer.


    21. Nginx Repair

    Suppose:

    Expected:
    php8.3-fpm.sock
    
    Actual:
    php8.2-fpm.sock

    The plan can identify:

    /etc/nginx/sites-enabled/example.com.conf

    as the affected file.


    22. Backup Before Modification

    The plan must include:

    BACKUP_FILE

    before:

    WRITE_FILE

    Example:

    1. Backup configuration
    2. Modify configuration
    3. nginx -t
    4. Reload nginx
    5. Health check

    23. Backup Location

    Use your CHP backup/configuration area.

    For example:

    /var/lib/cresignsys/repair-backups/

    Structure:

    /var/lib/cresignsys/repair-backups/
    └── example.com/
        └── RP-000021/
            └── nginx-example.com.conf

    24. Never Overwrite the Previous Repair Backup

    Each plan gets a unique directory:

    RP-000021
    RP-000022
    RP-000023

    This makes rollback traceable.


    25. File Hash

    Before modification, record:

    SHA-256

    of the original file.

    For example:

    old_sha256:
    abc123...

    After modification:

    new_sha256:
    def456...

    This provides verification.


    26. Prevent Stale Repair Plans

    This is very important.

    Suppose the plan was created at:

    16:00

    and actual configuration changes manually at:

    16:30

    The plan may now be outdated.

    Therefore before applying a plan:

    RECONCILE AGAIN

    27. Apply Preconditions

    Before applying:

    Plan created:
    PHP 8.2 → 8.3
    
    Current:
    PHP 8.2 → 8.3

    Good.

    If current state is now:

    PHP 8.4

    then:

    PLAN STALE

    and application must stop.


    28. Plan Fingerprint

    Store a fingerprint of important state.

    For example:

    expected_hash

    based on:

    web root
    PHP target
    Nginx configuration hash
    SSL state

    Before application:

    current fingerprint == plan fingerprint?

    If not:

    STALE

    29. Repair Plan Expiration

    Plans should eventually expire.

    For example:

    created:
    16:00
    
    expires:
    18:00

    If an old plan is used:

    Plan:
    EXPIRED

    Generate a fresh plan.


    30. Why This Matters

    Without stale-plan detection:

    Operator A:
    creates plan
    
    Operator B:
    changes server
    
    Operator A:
    applies old plan

    The old plan could overwrite the newer change.

    CHP must prevent this.


    31. Validation Before Approval

    Run:

    hosting-repair-plan
            ↓
    validate prerequisites
            ↓
    VALID

    Example:

    PHP 8.3-FPM installed       YES
    PHP 8.3 socket available    YES
    Nginx config found          YES
    Backup location writable    YES
    Site currently reachable    YES

    Then:

    Plan:
    VALID

    32. Approval

    Eventually:

    sudo hosting-repair-approve RP-000021

    This changes:

    VALID

    to:

    APPROVED

    But don’t implement approval yet.

    First make sure the plan generation works correctly.


    33. Human-Readable Plan

    The first version should output:

    CresignSys Repair Plan
    ======================
    
    Plan ID:
    RP-000021
    
    Site:
    example.com
    
    Risk:
    MEDIUM
    
    Status:
    VALID
    
    Problem:
    PHP-FPM configuration drift
    
    Current:
    8.2
    
    Expected:
    8.3
    
    Actions:
    
    1. Backup Nginx configuration
    2. Change PHP-FPM socket
    3. Validate Nginx configuration
    4. Reload Nginx
    5. Run site health check
    
    Rollback:
    Restore previous Nginx configuration
    
    No changes have been made.

    34. JSON Plan

    sudo hosting-repair-plan example.com --json

    Example:

    {
      "plan_id": "RP-000021",
      "site": "example.com",
      "status": "VALID",
      "risk": "MEDIUM",
      "problem": "PHP_VERSION_DRIFT",
      "actions": [
        {
          "sequence": 1,
          "type": "BACKUP_FILE"
        },
        {
          "sequence": 2,
          "type": "UPDATE_NGINX_PHP_TARGET"
        },
        {
          "sequence": 3,
          "type": "NGINX_CONFIG_TEST"
        },
        {
          "sequence": 4,
          "type": "RELOAD_NGINX"
        },
        {
          "sequence": 5,
          "type": "HEALTH_CHECK"
        }
      ]
    }

    35. Repair Plan for SSL

    Suppose:

    SSL:
    EXPIRING

    A future plan might be:

    Problem:
    CERTIFICATE_EXPIRING
    
    Proposed action:
    Renew certificate
    
    Prerequisites:
    DNS valid
    Port 80/443 accessible
    ACME configuration available
    
    Risk:
    MEDIUM

    Don’t implement certificate renewal yet.

    SSL renewal deserves its own carefully tested subsystem.


    36. Repair Plan for Ownership

    Suppose:

    Expected:
    www-data:www-data
    
    Actual:
    root:root

    The plan might be:

    Action:
    SET_OWNER
    
    Old:
    root:root
    
    New:
    www-data:www-data

    But this should be considered MEDIUM/HIGH risk because recursively changing ownership can break applications.


    37. Never Recursively Change Ownership by Default

    Avoid automatically proposing:

    chown -R www-data:www-data /

    or even:

    chown -R www-data:www-data /storage/websites/example.com

    without analyzing:

    uploads
    cache
    application files
    shared directories
    symlinks
    runtime directories

    The repair engine must have precise targets.


    38. Symlink Safety

    Before modifying a file:

    readlink

    should be used to determine whether the target is a symlink.

    A repair engine must not blindly follow a symlink and modify an unexpected file.

    This is especially important for:

    Nginx configuration
    WordPress uploads
    certificate files
    site directories

    39. Path Validation

    Before any future write:

    target path

    must be validated against an allowed directory.

    For example:

    Allowed:
     /etc/nginx/sites-enabled/
     /storage/websites/example.com/
     /var/lib/cresignsys/

    Reject:

    /etc/passwd
    /etc/shadow
    /home/other-user/

    unless explicitly supported by the operation.


    40. Never Trust Database Paths Blindly

    Even if the database contains:

    web_root=/storage/websites/example.com/public

    the repair engine should validate it before writing.

    Database data is input.

    Always verify:

    path exists
    path is expected
    path is inside allowed root

    41. Repair Plan Validation Layers

    Use:

    Layer 1
    Syntax validation
    
    Layer 2
    Path validation
    
    Layer 3
    Prerequisite validation
    
    Layer 4
    Risk validation
    
    Layer 5
    State/fingerprint validation
    
    Layer 6
    Backup validation

    Only then:

    VALID

    42. Repair Plan State Machine

    DRAFT
      │
      ▼
    VALIDATING
      │
      ├── failure ──→ BLOCKED
      │
      ▼
    VALID
      │
      ▼
    APPROVED
      │
      ▼
    APPLYING
      │
      ├── failure ──→ FAILED
      │
      ▼
    APPLIED
      │
      ▼
    VERIFIED

    A later rollback can transition:

    APPLIED
       ↓
    ROLLING_BACK
       ↓
    ROLLED_BACK

    43. VERIFIED Is Important

    Don’t consider:

    APPLIED

    equal to:

    WORKING

    Example:

    Nginx configuration changed

    successfully, but then:

    HTTP 502

    occurs.

    Therefore:

    APPLIED

    must be followed by:

    VERIFY

    44. Apply Loop

    The eventual repair engine should execute:

    Pre-check
       ↓
    Backup
       ↓
    Action 1
       ↓
    Action 2
       ↓
    Validation
       ↓
    Reload/restart if required
       ↓
    Health check
       ↓
    Reconcile
       ↓
    Verified

    45. Failure Handling

    Suppose:

    Backup:
    SUCCESS
    
    Modify Nginx:
    SUCCESS
    
    nginx -t:
    FAIL

    Do not reload Nginx.

    Instead:

    ROLLBACK

    to the previous configuration.


    46. Transaction-Like Repair

    Unlike SQLite transactions, server changes cannot always be rolled back automatically.

    Therefore CHP needs:

    BACKUP
    +
    VALIDATION
    +
    ROLLBACK

    to approximate transactional behavior.


    47. Repair Journal

    Every action should be recorded:

    repair_plan_items
    
    sequence | action | status
    ---------|--------|--------
    1        | backup | SUCCESS
    2        | write  | SUCCESS
    3        | test   | FAILED
    4        | reload | SKIPPED
    5        | rollback | SUCCESS

    This creates a complete repair journal.


    48. Never Hide Skipped Actions

    If Nginx validation fails:

    Reload:
    SKIPPED

    not:

    Reload:
    SUCCESS

    This is important for auditing.


    49. Example Failed Repair

    Repair RP-000021
    ================
    
    1. Backup configuration
       SUCCESS
    
    2. Modify configuration
       SUCCESS
    
    3. nginx -t
       FAILED
    
    4. Reload nginx
       SKIPPED
    
    5. Rollback
       SUCCESS
    
    Final:
    ROLLED_BACK

    50. Repair Operation

    Record:

    operation:
    REPAIR

    with:

    plan_id:
    RP-000021
    
    status:
    FAILED
    
    result:
    ROLLED_BACK

    The operator can then see exactly what happened.


    51. Audit Trail

    The system should eventually answer:

    Who approved this repair?
    When?
    What changed?
    Which files changed?
    What was the old value?
    What was the new value?
    Did validation pass?
    Was rollback required?

    This becomes critical when managing multiple customer websites.


    52. Operator Identity

    For the initial local CLI:

    operator:
    root

    But don’t assume root is a useful long-term identity.

    The future web control panel should have authenticated users.

    Then:

    operator_id

    can be stored with:

    repair_plan
    operation
    approval

    53. Approval History

    Eventually:

    repair_approvals
    
    id
    plan_id
    operator_id
    approved_at
    decision
    comment

    Example:

    RP-000021
    Approved by:
    admin
    
    Decision:
    APPROVED
    
    Time:
    2026-08-13 16:25

    54. Repair Plan CLI

    The eventual command family:

    hosting-repair-plan
    hosting-repair-validate
    hosting-repair-approve
    hosting-repair
    hosting-repair-rollback

    But don’t implement all five now.

    Lesson 084 only builds:

    hosting-repair-plan

    55. First Repair Types

    Start with only one:

    PHP_VERSION

    Why?

    Because it exercises:

    drift detection
    prerequisites
    file backup
    configuration modification
    syntax validation
    service reload
    health verification
    rollback

    It is a good architecture test.


    56. But Don’t Apply It Yet

    Even though we are designing PHP repair, Lesson 084 remains:

    PLAN ONLY

    No:

    sed -i
    systemctl reload
    systemctl restart

    inside the plan-generation command.


    57. PHP Plan Analyzer

    Create:

    sudo nano /etc/cresignsys/lib/repair-plan.sh

    Add:

    analyze_php_drift
    create_php_repair_plan
    validate_php_prerequisites

    58. Analyze Drift

    The analyzer should first run reconciliation.

    If:

    PHP:
    MATCH

    then:

    No repair plan required.

    If:

    PHP:
    DRIFT

    continue.


    59. Determine Current Configuration

    Discover:

    current PHP-FPM socket

    For example:

    /run/php/php8.2-fpm.sock

    Expected:

    /run/php/php8.3-fpm.sock

    60. Verify Target Exists

    Check:

    test -S /run/php/php8.3-fpm.sock

    If true:

    Target:
    AVAILABLE

    If false:

    Target:
    UNAVAILABLE

    Then plan:

    BLOCKED

    61. Verify Service

    Check:

    systemctl is-active php8.3-fpm

    If active:

    PHP 8.3-FPM:
    READY

    If not:

    BLOCKED

    62. Verify Nginx File

    Determine:

    /etc/nginx/sites-enabled/example.com.conf

    Check:

    test -f "$NGINX_CONFIG"

    Then:

    Nginx configuration:
    FOUND

    63. Verify Configuration Ownership

    Record:

    owner
    group
    permissions

    before modification.

    For example:

    root:root
    0644

    The repair engine should preserve these.


    64. Verify File Hash

    sha256sum "$NGINX_CONFIG"

    Store:

    original_hash

    in the repair plan.


    65. Plan Fingerprint

    A plan could contain:

    state_fingerprint:
    SHA256(...)

    constructed from:

    current PHP socket
    Nginx configuration hash
    site ID

    Before application, calculate again.

    If different:

    STALE

    66. Plan Example

    CresignSys Repair Plan
    ======================
    
    Plan:
    RP-000021
    
    Site:
    example.com
    
    Problem:
    PHP_VERSION_DRIFT
    
    Current:
    8.2
    
    Expected:
    8.3
    
    Target socket:
    /run/php/php8.3-fpm.sock
    
    Target service:
    php8.3-fpm
    
    Prerequisites:
    [OK] PHP-FPM available
    [OK] PHP-FPM running
    [OK] Nginx configuration found
    [OK] Configuration backup possible
    [OK] nginx validation available
    
    Risk:
    MEDIUM
    
    Status:
    VALID

    67. Proposed Actions

    1. Backup:
       /etc/nginx/sites-enabled/example.com.conf
    
    2. Update:
       php8.2-fpm.sock
       →
       php8.3-fpm.sock
    
    3. Run:
       nginx -t
    
    4. Reload:
       nginx
    
    5. Verify:
       HTTPS
    
    6. Reconcile:
       example.com

    68. No Changes Yet

    End with:

    NO CHANGES HAVE BEEN MADE.

    This line should always appear in hosting-repair-plan.

    It makes the command’s safety boundary explicit.


    69. What We Are Building

    The CHP architecture is becoming:

                    SITE
                     │
                     ▼
                 DISCOVERY
                     │
                     ▼
                RECONCILIATION
                     │
                 ┌───┴───┐
                 │       │
               MATCH    DRIFT
                 │       │
                 │       ▼
                 │   REPAIR PLAN
                 │       │
                 │       ▼
                 │   VALIDATION
                 │       │
                 │       ▼
                 │    APPROVAL
                 │       │
                 │       ▼
                 │      APPLY
                 │       │
                 │       ▼
                 └── VERIFY

    70. Lesson 084 — Core Principle

    A professional hosting platform should not respond to:

    DRIFT

    with:

    CHANGE

    It should respond:

    DRIFT
     ↓
    UNDERSTAND
     ↓
    PLAN
     ↓
    VALIDATE
     ↓
    APPROVE
     ↓
    CHANGE
     ↓
    VERIFY

    This gives CHP a controlled path from observation to automation without turning the management system into an uncontrolled shell-command executor.


    Next Lesson — 085

    The next step is the actual repair execution engine:

    sudo hosting-repair RP-000021

    It will implement:

    1. Verify plan
    2. Acquire site lock
    3. Reconcile again
    4. Check plan fingerprint
    5. Create backup
    6. Apply one action
    7. Validate
    8. Apply next action
    9. Run health check
    10. Run reconciliation
    11. Mark VERIFIED

    The most important part will be rollback:

    BACKUP
      ↓
    CHANGE
      ↓
    VALIDATION FAILS
      ↓
    AUTOMATIC ROLLBACK
      ↓
    HEALTH CHECK
      ↓
    ROLLED_BACK

    This will be the first CHP component that is allowed to modify a live website, so its safety boundaries need to be considerably stricter than the discovery and monitoring components.

  • CresignSys Learn Lesson — 083

    Build the Unified hosting-site-status

    We now have three separate capabilities:

    hosting-db-import
            ↓
    KNOW THE SITE
    
    hosting-reconcile
            ↓
    KNOW CONFIGURATION DRIFT
    
    hosting-health
            ↓
    KNOW RUNTIME HEALTH

    The next step is to create one command that brings everything together.

    sudo hosting-site-status example.com

    1. Goal

    The command should answer, in one screen:

    What is this site?
    Is it configured correctly?
    Is it currently working?
    Is SSL healthy?
    Is DNS correct?
    Are backups available?
    Is the server under pressure?
    What happened recently?

    2. Status Architecture

                        SITE
                         │
              ┌──────────┼──────────┐
              ▼          ▼          ▼
           Identity   Configuration Health
              │          │          │
              ▼          ▼          ▼
           Domains   Reconcile    Runtime
              │          │          │
              └──────────┼──────────┘
                         ▼
                      Backups
                         │
                         ▼
                     Operations
                         │
                         ▼
                   STATUS ENGINE
                         │
                         ▼
                UNIFIED SITE STATUS

    3. Create Command

    sudo nano /usr/local/bin/hosting-site-status

    Start with:

    #!/usr/bin/env bash
    
    set -Eeuo pipefail

    4. Load Libraries

    source /etc/cresignsys/hosting.conf
    source /etc/cresignsys/lib/common.sh
    source /etc/cresignsys/lib/logging.sh
    source /etc/cresignsys/lib/domain.sh
    source /etc/cresignsys/lib/database.sh
    source /etc/cresignsys/lib/discovery.sh
    source /etc/cresignsys/lib/reconcile.sh
    source /etc/cresignsys/lib/health.sh

    5. Require Root

    require_root

    6. Validate Domain

    DOMAIN="$(normalize_domain "$1")"
    validate_domain "$DOMAIN"

    Usage:

    sudo hosting-site-status learn.cresignsys.com

    7. Find Site

    First query:

    domains
       ↓
    site_id
       ↓
    sites

    If the site isn’t registered:

    SITE:
    NOT REGISTERED

    Then suggest:

    hosting-db-import example.com

    Do not automatically import.


    8. Header

    The output should start with:

    CresignSys Site Status
    ======================
    
    Site:
    learn.cresignsys.com
    
    Site ID:
    7
    
    Management:
    VERIFIED

    9. Identity Section

    Show:

    Identity
    --------
    
    Primary Domain:
    learn.cresignsys.com
    
    Web Root:
    /storage/websites/learn.cresignsys.com/public
    
    Application:
    WORDPRESS
    
    Owner:
    www-data:www-data

    10. Management State

    Display:

    Management:
    READ_ONLY

    or:

    Management:
    MANAGED

    This is important because an imported website may be monitored but not yet controlled by CHP.


    11. Domain Section

    Example:

    Domains
    -------
    
    learn.cresignsys.com
      Type: PRIMARY
      DNS:  CORRECT
      SSL:  ACTIVE
    
    www.learn.cresignsys.com
      Type: ALIAS
      DNS:  CORRECT
      SSL:  ACTIVE

    12. Don’t Duplicate Everything

    The site-status command should summarize.

    Detailed information remains available through:

    hosting-reconcile
    hosting-health
    hosting-info

    So:

    hosting-site-status

    is the dashboard.


    13. Configuration Section

    Run reconciliation:

    Configuration
    -------------
    
    Filesystem:
    MATCH
    
    Nginx:
    MATCH
    
    PHP:
    DRIFT
    
    Database:
    MATCH
    
    SSL:
    MATCH
    
    DNS:
    MATCH
    
    Overall:
    DRIFT

    14. Runtime Section

    Run health:

    Runtime Health
    --------------
    
    HTTP:
    HEALTHY
    
    HTTPS:
    HEALTHY
    
    TLS:
    HEALTHY
    
    Nginx:
    HEALTHY
    
    PHP-FPM:
    HEALTHY
    
    MySQL:
    HEALTHY
    
    WordPress:
    HEALTHY
    
    Overall:
    HEALTHY

    15. Combined Example

    This is an important scenario:

    Configuration:
    DRIFT
    
    Runtime:
    HEALTHY

    For example:

    PHP expected:
    8.3
    
    PHP actual:
    8.2

    but the site still works.

    The overall site status should not be DOWN.


    16. Status Priority

    Use separate states:

    RUNTIME:
    HEALTHY
    WARNING
    DEGRADED
    DOWN
    UNKNOWN

    and:

    CONFIGURATION:
    MATCH
    DRIFT
    UNKNOWN

    Then calculate a final operational state.


    17. Final Status Logic

    A practical first version:

    Runtime DOWN
        ↓
    OVERALL DOWN
    
    Runtime DEGRADED
        ↓
    OVERALL DEGRADED
    
    Runtime WARNING
        ↓
    OVERALL WARNING
    
    Runtime HEALTHY
    +
    Configuration DRIFT
        ↓
    OVERALL WARNING
    
    Runtime HEALTHY
    +
    Configuration MATCH
        ↓
    OVERALL HEALTHY

    18. Why Drift Becomes WARNING

    Suppose:

    PHP expected: 8.3
    PHP actual:   8.2

    but:

    HTTPS:
    200 OK

    Calling the site:

    DOWN

    would be misleading.

    Instead:

    OVERALL:
    WARNING

    is more accurate.


    19. Critical Health Overrides

    If:

    HTTPS:
    DOWN

    then:

    OVERALL:
    DOWN

    even if:

    Configuration:
    MATCH

    Because the website isn’t currently reachable.


    20. Example Status

    CresignSys Site Status
    ======================
    
    Site:
    example.com
    
    Configuration:
    DRIFT
    
    Runtime:
    HEALTHY
    
    Overall:
    WARNING

    This immediately communicates the situation.


    21. SSL Summary

    Show:

    SSL
    ---
    
    Status:
    ACTIVE
    
    Issuer:
    Let's Encrypt
    
    Expires:
    2026-11-20
    
    Days Remaining:
    99

    If near expiration:

    Status:
    WARNING

    22. DNS Summary

    DNS
    ---
    
    A Record:
    CORRECT
    
    Server:
    YOUR_SERVER_IP
    
    Resolved:
    YOUR_SERVER_IP

    If different:

    A Record:
    DRIFT

    23. Backup Summary

    Don’t list every backup.

    Show:

    Backups
    -------
    
    Latest:
    2026-08-13 02:00
    
    Count:
    12
    
    Verified:
    12
    
    Status:
    HEALTHY

    If:

    Verified:
    9 / 12

    show:

    Status:
    WARNING

    24. Backup Age

    A very useful metric is:

    Latest Backup Age:
    14 hours

    Suppose policy says:

    < 24 hours:
    HEALTHY
    24–48 hours:
    WARNING
    > 48 hours:
    DEGRADED

    Then status can be calculated.

    This policy should eventually be configurable per hosting plan.


    25. Resource Summary

    Display:

    Resources
    ---------
    
    Disk:
    65%
    
    Memory Available:
    8.2 GB
    
    Load:
    0.42
    
    Status:
    HEALTHY

    If disk is high:

    Disk:
    91%
    
    Status:
    WARNING

    26. WordPress Summary

    For WordPress:

    WordPress
    ---------
    
    Version:
    6.8.2
    
    Installation:
    HEALTHY
    
    Database:
    HEALTHY
    
    Core:
    INSTALLED

    Don’t display sensitive configuration.


    27. PHP Summary

    PHP
    ---
    
    Configured:
    8.3
    
    Running:
    8.3
    
    Service:
    HEALTHY
    
    Socket:
    AVAILABLE

    If drift:

    Configured:
    8.3
    
    Running:
    8.2
    
    Configuration:
    DRIFT
    
    Runtime:
    HEALTHY

    28. Nginx Summary

    Nginx
    -----
    
    Service:
    HEALTHY
    
    Configuration:
    VALID
    
    Site:
    LOADED

    This combines reconciliation and health information.


    29. Recent Operations

    Show the last few operations:

    Recent Operations
    -----------------
    
    2026-08-13 14:20
    RECONCILE
    DRIFT
    
    2026-08-13 13:00
    HEALTH CHECK
    HEALTHY
    
    2026-08-13 02:00
    BACKUP
    SUCCESS
    
    2026-08-12 14:00
    RECONCILE
    MATCH

    Only show the last 5–10 records.


    30. Why Operations Matter

    Suppose the site suddenly becomes unhealthy.

    The operator can immediately ask:

    What changed?

    and see:

    14:00 REPAIR
    14:01 HEALTH DOWN

    This creates a basic operational timeline.


    31. Final Summary

    At the bottom:

    Overall Status
    --------------
    
    Configuration:
    DRIFT
    
    Runtime:
    HEALTHY
    
    Backups:
    HEALTHY
    
    SSL:
    HEALTHY
    
    Resources:
    HEALTHY
    
    FINAL:
    WARNING

    32. Status Colors

    For a future web interface:

    HEALTHY → green
    WARNING  → yellow
    DEGRADED → orange
    DOWN     → red
    UNKNOWN  → gray

    Don’t hard-code terminal colors into the underlying health logic.

    The CLI presentation layer can add colors later.


    33. JSON Output

    Support:

    sudo hosting-site-status example.com --json

    Example:

    {
      "site": {
        "id": 7,
        "domain": "example.com",
        "application": "WORDPRESS",
        "management": "VERIFIED"
      },
      "configuration": {
        "status": "DRIFT"
      },
      "runtime": {
        "status": "HEALTHY"
      },
      "ssl": {
        "status": "HEALTHY"
      },
      "dns": {
        "status": "HEALTHY"
      },
      "backups": {
        "status": "HEALTHY"
      },
      "resources": {
        "status": "HEALTHY"
      },
      "overall": "WARNING"
    }

    34. This JSON Becomes the API Contract

    Later the CHP web panel can consume:

    hosting-site-status --json

    or, preferably, call the same internal library/API.

    The web dashboard should not scrape human-readable terminal output.


    35. Don’t Duplicate Business Logic

    Bad architecture:

    CLI
     └── own health calculations
    
    Web Panel
     └── different health calculations
    
    API
     └── another health calculation

    Better:

                  CHP Core
                     │
           ┌─────────┼─────────┐
           ▼         ▼         ▼
          CLI       API      Dashboard

    All use the same status engine.


    36. Build a Status Library

    Create:

    sudo nano /etc/cresignsys/lib/status.sh

    Functions:

    calculate_runtime_status
    calculate_configuration_status
    calculate_backup_status
    calculate_resource_status
    calculate_overall_status

    37. Overall Status Function

    Conceptually:

    calculate_overall_status() {
        local runtime="$1"
        local config="$2"
        local backup="$3"
        local resources="$4"
    
        # Critical runtime failure wins.
        if [[ "$runtime" == "DOWN" ]]; then
            echo "DOWN"
            return
        fi
    
        if [[ "$runtime" == "DEGRADED" ]]; then
            echo "DEGRADED"
            return
        fi
    
        if [[ "$runtime" == "WARNING" ||
              "$config" == "DRIFT" ||
              "$backup" == "WARNING" ||
              "$resources" == "WARNING" ]]; then
            echo "WARNING"
            return
        fi
    
        echo "HEALTHY"
    }

    This is only the first policy.


    38. Don’t Make UNKNOWN Look Healthy

    If:

    DNS:
    UNKNOWN

    and:

    HTTPS:
    UNKNOWN

    the final state should not be:

    HEALTHY

    Use:

    UNKNOWN

    or:

    WARNING

    depending on which critical checks could not be performed.


    39. Critical Check Policy

    Define:

    Critical:
    DNS
    HTTPS
    HTTP

    for public sites.

    If all three are:

    UNKNOWN

    then:

    OVERALL:
    UNKNOWN

    rather than claiming the site is healthy.


    40. Management State Does Not Equal Health

    This distinction is critical.

    A site can be:

    Management:
    READ_ONLY
    
    Health:
    HEALTHY

    or:

    Management:
    MANAGED
    
    Health:
    DOWN

    Management state describes CHP’s authority.

    Health describes runtime condition.


    41. Example

    Site:
    example.com
    
    Management:
    READ_ONLY
    
    Configuration:
    MATCH
    
    Runtime:
    HEALTHY
    
    Overall:
    HEALTHY

    This is perfectly valid.


    42. Example Managed Site

    Site:
    shop.example.com
    
    Management:
    MANAGED
    
    Configuration:
    MATCH
    
    Runtime:
    DOWN
    
    Overall:
    DOWN

    CHP has management permission, but the site is currently unavailable.


    43. Security State

    Don’t include security in overall health yet.

    Eventually:

    Health:
    HEALTHY
    
    Security:
    WARNING

    For example:

    SSL:
    valid
    
    WordPress:
    outdated

    The site can still be operational.


    44. Four Operational Dimensions

    The eventual dashboard should have:

    ┌────────────────────────────────┐
    │ example.com                    │
    ├────────────────────────────────┤
    │ Runtime       HEALTHY          │
    │ Configuration DRIFT            │
    │ Backups       HEALTHY          │
    │ Security      WARNING          │
    └────────────────────────────────┘

    This is more useful than a single status.


    45. hosting-info vs hosting-site-status

    Use:

    hosting-info example.com

    for:

    metadata

    Use:

    hosting-site-status example.com

    for:

    current operational condition

    Use:

    hosting-reconcile example.com

    for:

    configuration investigation

    Use:

    hosting-health example.com

    for:

    runtime investigation

    46. Command Map

    hosting-info
          │
          └── WHAT IS THIS SITE?
    
    hosting-site-status
          │
          └── HOW IS THIS SITE NOW?
    
    hosting-reconcile
          │
          └── DOES CONFIG MATCH?
    
    hosting-health
          │
          └── IS IT WORKING?
    
    hosting-db-import
          │
          └── HOW DO WE REGISTER IT?

    47. Site Status API Model

    Eventually the same structure can be represented as:

    {
      "site_id": 7,
      "domain": "example.com",
      "runtime_status": "HEALTHY",
      "configuration_status": "DRIFT",
      "backup_status": "HEALTHY",
      "security_status": "WARNING",
      "overall_status": "WARNING"
    }

    This becomes the central object for the CHP dashboard.


    48. Database View

    Later you can create a view:

    CREATE VIEW site_status_summary AS
    SELECT
        s.id,
        s.primary_domain,
        s.management_state,
        s.service_state,
        s.health_state
    FROM sites s;

    But don’t put transient calculations permanently into sites.

    The site record should contain durable metadata.


    49. Durable vs Temporary Data

    Durable

    domain
    web_root
    site_user
    application_type
    management_state

    Observed

    PHP currently running
    certificate currently valid
    HTTP response time
    disk usage

    Historical

    health checks
    operations
    backups
    reconciliation events

    Keep these concepts separate.


    50. Status Data Model

    SITE
     │
     ├── configuration metadata
     │
     ├── current health
     │
     ├── health history
     │
     ├── configuration history
     │
     ├── backup history
     │
     └── operation history

    This will scale much better as CHP grows.


    51. Test the Command

    First:

    sudo chmod +x /usr/local/bin/hosting-site-status

    Then:

    sudo hosting-site-status learn.cresignsys.com

    Then:

    sudo hosting-site-status learn.cresignsys.com --json

    52. Test Healthy Site

    Expected:

    Runtime:
    HEALTHY
    
    Configuration:
    MATCH
    
    Backups:
    HEALTHY
    
    Resources:
    HEALTHY
    
    Overall:
    HEALTHY

    53. Test Configuration Drift

    Change nothing on the server.

    Use an existing difference, if available.

    Expected:

    Runtime:
    HEALTHY
    
    Configuration:
    DRIFT
    
    Overall:
    WARNING

    54. Test Runtime Failure Safely

    Do not stop production services merely to test the health engine.

    Instead use a controlled test environment or temporary test site.

    For example:

    test.example.com

    can deliberately return:

    HTTP 500

    Then:

    hosting-health test.example.com

    should detect:

    DOWN

    55. Never Test by Breaking Production

    Avoid:

    systemctl stop nginx

    on a live production site just to test monitoring.

    CHP should be developed with:

    test site
    staging environment

    for failure simulations.


    56. Site Status Example

    A polished result could look like:

    ╔══════════════════════════════════════════╗
    ║       CRESIGNSYS SITE STATUS             ║
    ╠══════════════════════════════════════════╣
    ║ Site: learn.cresignsys.com               ║
    ║ App:  WordPress                           ║
    ║ Mode: VERIFIED                            ║
    ╠══════════════════════════════════════════╣
    ║ RUNTIME          HEALTHY                  ║
    ║ CONFIGURATION    MATCH                    ║
    ║ SSL              HEALTHY                  ║
    ║ DNS              HEALTHY                  ║
    ║ BACKUPS          HEALTHY                  ║
    ║ RESOURCES        HEALTHY                  ║
    ╠══════════════════════════════════════════╣
    ║ OVERALL          HEALTHY                  ║
    ╚══════════════════════════════════════════╝

    The exact visual formatting can be improved later.


    57. Why This Command Is Important

    Previously, you had to run:

    hosting-info
    hosting-health
    hosting-reconcile
    hosting-backup

    separately.

    Now:

    hosting-site-status example.com

    becomes the single operational entry point.


    58. Future Control Panel

    This command maps directly to a web dashboard:

    ┌─────────────────────────────────────────┐
    │ CresignSys Hosting Platform             │
    ├─────────────────────────────────────────┤
    │                                         │
    │  Sites                                  │
    │                                         │
    │  example.com       HEALTHY              │
    │  learn.com         WARNING              │
    │  shop.com          DOWN                 │
    │  blog.com          HEALTHY              │
    │                                         │
    └─────────────────────────────────────────┘

    Click:

    shop.com

    and show:

    Runtime
    Configuration
    SSL
    DNS
    Backups
    Resources
    Operations

    59. The CHP Control Loop

    We now have a complete observation loop:

                 EXISTING SERVER
                        │
                        ▼
                  DISCOVERY
                        │
                        ▼
                  CHP DATABASE
                        │
                        ▼
                RECONCILIATION
                        │
                        ▼
                  CONFIG STATE
                        │
                        ▼
                     HEALTH
                        │
                        ▼
                  RUNTIME STATE
                        │
                        ▼
                 SITE STATUS

    The next stage is where CHP becomes a real hosting management platform:

    SITE STATUS
         │
         ▼
    DETECT PROBLEM
         │
         ▼
    CREATE REPAIR PLAN
         │
         ▼
    VALIDATE
         │
         ▼
    APPLY CHANGE
         │
         ▼
    VERIFY

    60. Lesson 083 — Core Principle

    The unified status command must not hide complexity.

    It should summarize it.

    For example:

    OVERALL:
    WARNING

    must still allow the operator to see:

    Why?
    
    Configuration:
    DRIFT
    
    PHP:
    Expected 8.3
    Actual 8.2
    
    Runtime:
    HEALTHY

    That makes CHP operationally useful rather than just another dashboard showing red and green indicators.


    Next Lesson — 084

    Build the CHP Repair Engine — Safely

    Now we can begin the write side of CHP.

    The first repair engine should not immediately change anything.

    We will build:

    sudo hosting-repair-plan example.com

    which converts detected drift into a proposed repair plan:

    DRIFT
      ↓
    ANALYZE
      ↓
    PROPOSE
      ↓
    SHOW EXACT CHANGES
      ↓
    VALIDATE
      ↓
    APPROVAL REQUIRED

    For example:

    PHP VERSION DRIFT
    
    Expected:
    8.3
    
    Actual:
    8.2
    
    Proposed repair:
    Update Nginx site configuration
    from PHP 8.2-FPM
    to PHP 8.3-FPM
    
    Files affected:
    1 Nginx configuration
    
    Services affected:
    Nginx
    
    Rollback:
    Available
    
    Risk:
    MEDIUM
    
    NO CHANGES HAVE BEEN MADE

    This will establish the most important rule for CHP’s write operations:

    Never change the server directly from a detected problem. First generate a repair plan, validate it, and only then apply it.

  • CresignSys Learn — Lesson 082

    Build the CHP Health Engine

    We now have two separate systems:

    RECONCILIATION
        ↓
    "Does the actual configuration match CHP?"

    and:

    HEALTH
        ↓
    "Is the website actually working?"

    These must remain separate.


    1. Health Command

    Create:

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

    Usage:

    sudo hosting-health example.com

    Also:

    sudo hosting-health example.com --json
    sudo hosting-health example.com --verbose

    2. Health Architecture

                     hosting-health
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
            SERVER       WEBSITE      DATA
              │            │            │
              ▼            ▼            ▼
            CPU          HTTP         MySQL
            RAM          HTTPS        WordPress
            Disk         TLS
            Load         DNS
            Nginx        Redirects
            PHP-FPM      Response

    3. Health vs Reconciliation

    Example:

    CHP expects PHP 8.3
    Actual PHP 8.2

    Reconciliation:

    DRIFT

    But if PHP 8.2 is running and the website works:

    Health:
    HEALTHY

    Therefore:

    Configuration:
    DRIFT
    
    Runtime:
    HEALTHY

    Both can be true.


    4. Health Result Categories

    Use:

    HEALTHY
    WARNING
    DEGRADED
    DOWN
    UNKNOWN
    ERROR

    HEALTHY

    Everything important is functioning.

    WARNING

    Working, but an issue needs attention.

    DEGRADED

    Part of the service is impaired.

    DOWN

    The site/service is unavailable.

    UNKNOWN

    A reliable health determination cannot be made.

    ERROR

    The health checker itself failed.


    5. Health Checks

    The first version should check:

    1. DNS
    2. HTTP
    3. HTTPS
    4. TLS
    5. Nginx
    6. PHP-FPM
    7. MySQL
    8. WordPress
    9. Disk
    10. Memory
    11. Load

    6. Check 1 — DNS

    Use:

    dig +short A example.com

    Expected:

    SERVER_IPV4

    Result:

    DNS:
    HEALTHY

    If DNS doesn’t resolve:

    DNS:
    DOWN

    But distinguish between:

    DNS unavailable

    and:

    DNS points somewhere else

    The second is configuration drift rather than necessarily a runtime outage.


    7. DNS Health Function

    Add to:

    /etc/cresignsys/lib/health.sh

    Function:

    health_dns

    Conceptually:

    health_dns() {
        local domain="$1"
    
        if dig +short A "$domain" | grep -q .; then
            echo "HEALTHY"
        else
            echo "DOWN"
        fi
    }

    Later compare the result against CHP’s expected server IP.


    8. HTTP Check

    Use:

    curl -I --max-time 10 http://example.com

    Possible response:

    HTTP/1.1 301 Moved Permanently

    A redirect is not automatically a failure.


    9. HTTP Status Categories

    Treat:

    2xx → HEALTHY
    3xx → HEALTHY/WARNING
    4xx → WARNING/DEGRADED
    5xx → DOWN
    timeout → DOWN
    connection failure → DOWN

    But don’t treat every 4xx identically.

    For example:

    401 Unauthorized

    may be intentional.


    10. HTTPS Check

    Use:

    curl -I --max-time 10 https://example.com

    Expected:

    HTTP/2 200

    or:

    HTTP/2 301

    depending on the site’s design.


    11. HTTP Redirect Chain

    A common configuration:

    http://example.com
           ↓
    301
           ↓
    https://example.com
           ↓
    200

    This is healthy.

    Test:

    curl -IL --max-time 10 http://example.com

    The health engine should understand the chain.


    12. Redirect Loop

    Bad example:

    HTTP
     ↓
    HTTPS
     ↓
    HTTP
     ↓
    HTTPS

    curl eventually fails.

    Report:

    HTTPS:
    DOWN
    
    Reason:
    REDIRECT_LOOP

    13. Excessive Redirects

    Even if a redirect chain eventually succeeds:

    HTTP
     ↓
    HTTPS
     ↓
    www
     ↓
    non-www
     ↓
    HTTPS

    it may be inefficient.

    Report:

    WARNING

    if the chain exceeds your configured threshold.

    For example:

    MAX_REDIRECTS=5

    14. HTTP Response Time

    Don’t only check status.

    Measure:

    curl -o /dev/null \
         -s \
         -w '%{http_code} %{time_total}\n' \
         https://example.com

    Example:

    200 0.342

    Store:

    HTTP status:
    200
    
    Response time:
    342 ms

    15. Response-Time Thresholds

    Initial thresholds could be:

    < 1 sec       HEALTHY
    1–3 sec       WARNING
    3–10 sec      DEGRADED
    > 10 sec      DOWN/TIMEOUT

    These should eventually be configurable.

    Don’t treat these as universal performance benchmarks.


    16. TLS Check

    Use:

    openssl s_client \
        -connect example.com:443 \
        -servername example.com

    But don’t dump the entire output to logs.

    Extract only:

    certificate subject
    issuer
    expiry
    verification result

    17. TLS Verification

    A healthy result:

    TLS:
    HEALTHY
    
    Certificate:
    VALID
    
    Expires:
    2026-11-20

    If certificate verification fails:

    TLS:
    DOWN

    18. Certificate Expiration Warning

    Example:

    30+ days:
    HEALTHY
    
    8–30 days:
    WARNING
    
    1–7 days:
    DEGRADED
    
    Expired:
    DOWN

    This should be configurable.


    19. Nginx Check

    Check:

    systemctl is-active nginx

    If active:

    Nginx:
    HEALTHY

    If inactive:

    Nginx:
    DOWN

    20. Nginx Configuration

    Separately run:

    nginx -t

    Possible:

    Service:
    HEALTHY
    
    Configuration:
    ERROR

    This is useful because a running Nginx process can still have a configuration problem waiting for the next reload.


    21. PHP-FPM Check

    Determine the site’s configured PHP-FPM service:

    php8.3-fpm

    Then:

    systemctl is-active php8.3-fpm

    Expected:

    PHP-FPM:
    HEALTHY

    22. PHP Socket

    Check:

    test -S /run/php/php8.3-fpm.sock

    If missing:

    PHP-FPM:
    DEGRADED

    or:

    DOWN

    depending on whether the service itself is running.


    23. PHP End-to-End Check

    Service status alone isn’t enough.

    You want:

    Browser
       ↓
    Nginx
       ↓
    PHP-FPM
       ↓
    PHP

    A simple PHP health endpoint can eventually be useful.

    For example:

    /health.php

    But don’t expose sensitive diagnostic information publicly.


    24. Better Health Endpoint

    Create a protected endpoint that returns:

    OK

    rather than:

    PHP version
    server variables
    database password
    filesystem paths

    For example:

    https://example.com/.well-known/chp-health

    could eventually return:

    OK

    if appropriate for your architecture.


    25. Don’t Expose Infrastructure Information

    Avoid public endpoints displaying:

    MYSQL_HOST
    DB_USER
    PHP_VERSION
    SERVER_IP
    filesystem paths
    environment variables

    A health endpoint should reveal as little as possible.


    26. MySQL Health

    For WordPress:

    wp db check

    is useful.

    Possible result:

    Database:
    HEALTHY

    If connection fails:

    Database:
    DOWN

    27. MySQL Service

    Also check:

    systemctl is-active mysql

    This gives:

    MySQL Service:
    HEALTHY

    while:

    WordPress Database:
    DOWN

    could indicate a connection/configuration problem.


    28. WordPress Health

    Check:

    wp core is-installed

    Then optionally:

    wp db check

    A WordPress site may have:

    Nginx:
    OK
    
    PHP:
    OK
    
    MySQL:
    OK
    
    WordPress:
    ERROR

    For example, WordPress files may be incomplete.


    29. WordPress HTTP Check

    The most important application-level check is often:

    HTTPS request
           ↓
    WordPress response

    If:

    HTTP 200

    the website is externally responding.

    This is often more meaningful than checking individual processes.


    30. WordPress Error Detection

    A 200 response does not guarantee the page is healthy.

    For example:

    HTTP 200

    could contain:

    Fatal error

    or:

    Database connection error

    Therefore application-specific checks can be added later.


    31. Disk Health

    Check:

    df -h

    For the website filesystem:

    df -h /storage/websites/example.com

    Example:

    Filesystem      Size  Used Avail Use%
    /dev/sdb        100G   65G   35G  65%

    32. Disk Thresholds

    Initial policy:

    < 80%       HEALTHY
    80–90%      WARNING
    90–95%      DEGRADED
    > 95%       CRITICAL

    The exact thresholds should be configurable.


    33. Inode Usage

    Disk space can be available while inodes are exhausted.

    Check:

    df -i /storage/websites/example.com

    This can detect:

    100% inode usage

    which can prevent new files from being created.


    34. Memory

    Check:

    free -m

    For overall server health, monitor:

    total
    used
    available

    Avoid using:

    used / total

    alone as the definition of memory pressure on Linux.

    available is generally more useful for this purpose.


    35. Load Average

    Use:

    uptime

    or:

    cat /proc/loadavg

    Example:

    0.25 0.31 0.28

    Interpret load relative to the number of CPU cores.


    36. CPU Count

    nproc

    If:

    4 CPUs

    and load is:

    0.5

    that’s generally light.

    If load remains:

    8+

    on a 4-core system, that deserves investigation.

    Don’t define a single universal load threshold.


    37. Website-Level vs Server-Level Health

    This distinction matters.

    A server may have:

    CPU:
    HIGH

    while:

    example.com:
    HTTP 200

    Therefore:

    Server health:
    WARNING
    
    Website health:
    HEALTHY

    Both can be true.


    38. Site Health Model

    The site health engine should primarily prioritize:

    DNS
    HTTP
    HTTPS
    TLS
    Nginx
    PHP
    Database
    Application

    while server resource metrics provide:

    WARNING

    unless they actually cause service failure.


    39. Health Score

    Avoid an overly simplistic:

    8 checks = 8 points

    because some checks are much more important.

    For example:

    HTTPS DOWN

    is more serious than:

    Disk at 82%

    Use severity instead.


    40. Severity Model

    Each check can produce:

    OK
    WARNING
    CRITICAL
    UNKNOWN

    Example:

    HTTP       OK
    HTTPS      OK
    TLS        OK
    PHP        OK
    MySQL      OK
    Disk       WARNING

    Overall:

    WARNING

    41. Critical Checks

    For a normal public website:

    DNS
    HTTPS
    HTTP

    are usually critical.

    For WordPress:

    Database
    PHP

    are also critical.


    42. Dependency Awareness

    Don’t report five independent failures when one root failure caused them.

    Example:

    PHP-FPM DOWN

    may cause:

    HTTP 502
    WordPress DOWN
    Application DOWN

    The health engine should understand this relationship.


    43. Example

    Instead of:

    HTTP:
    DOWN
    
    PHP:
    DOWN
    
    WordPress:
    DOWN
    
    Application:
    DOWN

    say:

    PHP-FPM:
    CRITICAL
    
    HTTP:
    DEGRADED
    Reason: upstream unavailable
    
    WordPress:
    NOT_REACHABLE
    
    Likely root cause:
    PHP-FPM failure

    This is much more useful.


    44. Dependency Graph

                        DNS
                         │
                         ▼
                      Nginx
                         │
                  ┌──────┴──────┐
                  ▼             ▼
               Static         PHP-FPM
                                │
                                ▼
                             MySQL
                                │
                                ▼
                            WordPress

    If PHP-FPM fails:

    PHP-FPM
       ↓
    WordPress
       ↓
    HTTP application response

    can also fail.


    45. Health Engine Library

    Create:

    sudo nano /etc/cresignsys/lib/health.sh

    Functions:

    health_dns
    health_http
    health_https
    health_tls
    health_nginx
    health_php
    health_mysql
    health_wordpress
    health_disk
    health_memory
    health_load
    health_site

    46. Don’t Mix Health and Reconciliation Functions

    Keep:

    reconcile_php()

    separate from:

    health_php()

    because they answer different questions.

    Reconciliation

    Expected PHP:
    8.3
    
    Actual PHP:
    8.2
    
    Result:
    DRIFT

    Health

    PHP-FPM:
    running
    
    Result:
    HEALTHY

    47. HTTP Health Function

    Conceptually:

    health_http() {
        local domain="$1"
    
        curl \
            --silent \
            --show-error \
            --location \
            --max-time 10 \
            --output /dev/null \
            --write-out '%{http_code} %{time_total}' \
            "https://${domain}"
    }

    Capture:

    status
    time

    separately.


    48. Curl Options

    Useful options:

    --max-time
    --connect-timeout
    --location
    --silent
    --show-error

    For example:

    curl \
      --connect-timeout 5 \
      --max-time 10 \
      --location \
      ...

    This prevents a broken site from hanging the entire health process.


    49. Health Timeout

    Every external check should have a timeout.

    Never allow:

    curl
    dig
    openssl
    mysql
    wp

    to hang indefinitely.

    The health engine should finish predictably.


    50. Parallel Health Checks

    Later, checks can run concurrently:

    DNS ──────┐
    HTTP ─────┤
    TLS ──────┤
    PHP ──────┤──→ Result
    MySQL ────┤
    Disk ─────┘

    But initially use sequential checks.

    Reliability is more important than squeezing a few seconds out of the first implementation.


    51. Health Output

    Example:

    CresignSys Site Health
    ======================
    
    Site:
    learn.cresignsys.com
    
    DNS
    ---
    Status:       HEALTHY
    A Record:     CORRECT
    
    HTTP
    ----
    Status:       HEALTHY
    HTTP Code:    301
    Response:     0.18 sec
    
    HTTPS
    -----
    Status:       HEALTHY
    HTTP Code:    200
    Response:     0.34 sec
    
    TLS
    ---
    Status:       HEALTHY
    Certificate:  VALID
    Expires:      2026-11-20
    
    Nginx
    -----
    Service:      HEALTHY
    Config:       VALID
    
    PHP-FPM
    -------
    Service:      HEALTHY
    Socket:       AVAILABLE
    
    MySQL
    -----
    Service:      HEALTHY
    Connection:   HEALTHY
    
    WordPress
    ---------
    Installation: HEALTHY
    Database:     HEALTHY
    
    Resources
    ---------
    Disk:         HEALTHY
    Memory:       HEALTHY
    Load:         HEALTHY
    
    Overall:
    HEALTHY

    52. Degraded Example

    CresignSys Site Health
    ======================
    
    HTTPS:
    HEALTHY
    
    PHP-FPM:
    HEALTHY
    
    MySQL:
    HEALTHY
    
    Disk:
    WARNING
    Usage: 87%
    
    Overall:
    WARNING

    The website works, but the server needs attention.


    53. Critical Example

    HTTPS:
    DOWN
    
    PHP-FPM:
    DOWN
    
    MySQL:
    HEALTHY
    
    Likely cause:
    PHP-FPM unavailable
    
    Overall:
    DOWN

    54. Health JSON

    Add:

    sudo hosting-health example.com --json

    Example:

    {
      "domain": "example.com",
      "overall": "HEALTHY",
      "checks": {
        "dns": {
          "status": "HEALTHY"
        },
        "https": {
          "status": "HEALTHY",
          "http_code": 200,
          "response_time": 0.34
        },
        "tls": {
          "status": "HEALTHY"
        },
        "nginx": {
          "status": "HEALTHY"
        },
        "php": {
          "status": "HEALTHY"
        },
        "mysql": {
          "status": "HEALTHY"
        },
        "wordpress": {
          "status": "HEALTHY"
        }
      }
    }

    55. Store Health Results

    The services table already has:

    status
    updated_at

    but we should not overwrite everything with transient health data.

    Eventually add a health-history table.


    56. health_checks Table

    Create in a future migration:

    CREATE TABLE health_checks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER NOT NULL,
        check_name TEXT NOT NULL,
        status TEXT NOT NULL,
        response_time_ms INTEGER,
        message TEXT,
        checked_at TEXT NOT NULL,
    
        FOREIGN KEY (site_id)
            REFERENCES sites(id)
            ON DELETE CASCADE
    );

    57. Why History?

    Suppose:

    14:00 HEALTHY
    15:00 HEALTHY
    16:00 WARNING
    17:00 DOWN
    18:00 HEALTHY

    You can identify an outage window.


    58. Don’t Store Every Check Forever

    If health checks run every 5 minutes:

    12 checks/hour
    288 checks/day

    for 100 sites:

    28,800 checks/day

    This grows quickly.

    Use retention.

    For example:

    raw checks:
    7 days

    and later:

    hourly aggregates:
    90 days

    59. Health Aggregation

    Eventually:

    Raw:
    5-minute checks
    
           ↓
    
    Hourly:
    availability percentage
    
           ↓
    
    Daily:
    uptime percentage

    Example:

    August 13
    Uptime:
    99.87%

    60. Uptime Calculation

    If a site has:

    288 checks/day

    and:

    2 failed checks

    then approximate availability:

    286 / 288 × 100
    = 99.31%

    For production-grade monitoring, account for check failures and measurement uncertainty rather than treating every failed probe as definitive downtime.


    61. Health Endpoint for CHP

    Later the central CHP control panel can display:

    Sites
    ----------------------------------------
    example.com          HEALTHY
    shop.com             WARNING
    blog.com              DOWN
    medical.com           HEALTHY

    Clicking a site:

    example.com

    shows:

    Health
    Reconciliation
    Backups
    SSL
    Domains
    Operations

    62. Site Status Combines Both Systems

    We now have:

    hosting-reconcile

    and:

    hosting-health

    Therefore:

    hosting-site-status example.com

    can combine them.


    63. Combined Status

    Example:

    CresignSys Site Status
    ======================
    
    Configuration:
    DRIFT
    
    Runtime:
    HEALTHY
    
    Overall:
    WARNING
    
    Configuration issue:
    PHP expected 8.3, actual 8.2

    This is much more informative than simply saying:

    Site:
    FAIL

    64. Four Dimensions

    A mature CHP status system can have:

    CONFIGURATION
    HEALTH
    BACKUP
    SECURITY

    Example:

    Configuration:
    DRIFT
    
    Health:
    HEALTHY
    
    Backup:
    HEALTHY
    
    Security:
    WARNING

    65. Future Security Checks

    The health engine can eventually include:

    certificate validity
    open ports
    file permissions
    disk permissions
    WordPress core integrity
    plugin vulnerabilities
    malware indicators

    But security scanning should remain a separate subsystem where possible.

    Don’t overload hosting-health.


    66. Health vs Monitoring

    hosting-health:

    Run once

    Monitoring:

    Run repeatedly

    So:

    hosting-health

    is the probe.

    A future:

    hosting-monitor

    will schedule probes and record history.


    67. Future Monitoring Architecture

                 scheduler
                     │
                     ▼
             hosting-health
                     │
                     ▼
              health_checks
                     │
                     ▼
                 alerting
                     │
            ┌────────┼────────┐
            ▼        ▼        ▼
          Email    Panel    WhatsApp*

    The notification mechanism should be designed separately.


    68. Don’t Alert on Every Failure

    If a single probe fails because of a temporary network issue:

    FAIL

    you don’t necessarily want an immediate outage alert.

    Use consecutive failures.

    Example:

    1 failure:
    record
    
    2 failures:
    warning
    
    3 consecutive failures:
    DOWN/ALERT

    The exact policy can be configured later.


    69. Recovery Detection

    If the site was:

    DOWN

    and then returns:

    HEALTHY

    record:

    RECOVERED

    This is useful for operations history.


    70. Health State Machine

    HEALTHY
       │
       ▼
    WARNING
       │
       ▼
    DEGRADED
       │
       ▼
    DOWN
       │
       ▼
    RECOVERING
       │
       ▼
    HEALTHY

    But don’t transition states on one noisy probe without considering consecutive failures.


    71. First Version Scope

    For now implement only:

    DNS
    HTTP
    HTTPS
    TLS
    Nginx
    PHP-FPM
    MySQL
    WordPress
    Disk
    Memory
    Load

    Don’t yet implement:

    alerting
    scheduler
    uptime history
    automatic restart
    automatic repair

    72. Automatic Restart Is Dangerous

    Do not immediately implement:

    systemctl restart php8.3-fpm

    when health fails.

    A failure may have a deeper cause:

    memory exhaustion
    configuration error
    database problem
    malicious traffic
    disk full

    Automatic restarts can hide the root problem.


    73. Safe First Response

    Health engine:

    DETECT
       ↓
    REPORT
       ↓
    LOG

    Later:

    DETECT
       ↓
    CLASSIFY
       ↓
    REPAIR PLAN
       ↓
    APPROVE
       ↓
    APPLY

    74. Test Sequence

    After implementing:

    sudo hosting-health learn.cresignsys.com

    Then:

    sudo hosting-health learn.cresignsys.com --json

    Then compare with:

    sudo hosting-reconcile learn.cresignsys.com

    You should see the difference between:

    configuration

    and:

    runtime

    75. Example Combined Interpretation

    Suppose:

    Reconciliation:
    DRIFT
    
    Health:
    HEALTHY

    Interpretation:

    The website is currently working, but its actual configuration differs from CHP’s recorded/desired configuration.

    Another case:

    Reconciliation:
    MATCH
    
    Health:
    DOWN

    Interpretation:

    Configuration appears correct, but a runtime failure is occurring.

    Another:

    Reconciliation:
    DRIFT
    
    Health:
    DOWN

    Interpretation:

    Configuration differs and the site is currently unavailable; investigate both, starting with the immediate runtime failure.


    76. CHP Operational Model

    We now have three important questions:

    QUESTION 1
    Does CHP know the site?
            ↓
    DATABASE
    QUESTION 2
    Does the server match CHP?
            ↓
    RECONCILIATION
    QUESTION 3
    Is the site working?
            ↓
    HEALTH

    Together:

    DATABASE
       +
    RECONCILIATION
       +
    HEALTH
       ↓
    SITE STATUS

    77. Updated CHP Architecture

                             CRESIGNSYS
                                  │
                    ┌─────────────┴─────────────┐
                    ▼                           ▼
              CONTROL PLANE                 DATA PLANE
                    │                           │
                    ▼                           ▼
               CHP DATABASE               Website Runtime
                    │                           │
           ┌────────┼────────┐          ┌───────┼────────┐
           ▼        ▼        ▼          ▼       ▼        ▼
         Sites    Domains  Backups     Nginx    PHP     MySQL
           │
           ▼
    Desired/known state
           │
           ├───────────────┐
           ▼               ▼
    RECONCILIATION       HEALTH
           │               │
           │               ├── DNS
           │               ├── HTTP
           │               ├── HTTPS
           │               ├── TLS
           │               ├── PHP
           │               └── Database
           │
           └───────┬───────┘
                   ▼
              SITE STATUS

    78. Lesson 082 — Core Principle

    The CHP health engine must be observational before it becomes corrective.

    Its job is to reliably answer:

    Is DNS working?
    Is HTTP working?
    Is HTTPS working?
    Is TLS valid?
    Is Nginx running?
    Is PHP-FPM running?
    Is MySQL available?
    Is WordPress functional?
    Is the filesystem healthy?
    Is the server under resource pressure?

    without changing the server.

    That gives us a safe foundation for the next stage.


    Next Lesson — 083

    Build the Unified hosting-site-status

    We now have:

    hosting-db-import
    hosting-reconcile
    hosting-health

    The next command will combine them:

    sudo hosting-site-status example.com

    It will produce a single operational view:

    SITE
    ├── Identity
    ├── Domains
    ├── Configuration
    ├── Runtime Health
    ├── SSL
    ├── DNS
    ├── WordPress
    ├── Backups
    ├── Resources
    └── Recent Operations

    and ultimately calculate:

    HEALTHY
    WARNING
    DEGRADED
    DOWN
    DRIFT
    UNKNOWN

    without confusing configuration drift with actual downtime.

  • CresignSys Learn — Lesson 081

    Build hosting-reconcile

    We now have two sources of information:

    CHP DATABASE
        │
        ▼
    Known / Desired State

    and:

    LIVE SERVER
        │
        ▼
    Observed / Actual State

    The reconciliation engine compares them.

    The first version must be read-only.


    1. Reconciliation Command

    Create:

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

    Usage:

    sudo hosting-reconcile example.com

    Also support:

    sudo hosting-reconcile example.com --json
    sudo hosting-reconcile example.com --verbose

    Do not add automatic repair yet.


    2. Core Principle

    Reconciliation answers:

    Is the actual server consistent with what CHP knows or expects?

    It does not answer:

    How should I change the server?

    That belongs to:

    hosting-repair

    3. Architecture

                        CHP DATABASE
                             │
                             ▼
                      EXPECTED STATE
                             │
                             │
                             ▼
                     RECONCILIATION
                             ▲
                             │
                             │
                      ACTUAL STATE
                             │
            ┌────────────────┼────────────────┐
            ▼                ▼                ▼
          Nginx             PHP              SSL
            │                │                │
            ▼                ▼                ▼
           DNS           WordPress         MySQL

    4. Reconciliation Result Types

    Use a small, consistent vocabulary:

    MATCH
    DRIFT
    MISSING
    UNEXPECTED
    UNKNOWN
    ERROR

    MATCH

    Expected and actual are consistent.

    DRIFT

    Both exist but differ.

    MISSING

    Expected component doesn’t exist.

    UNEXPECTED

    Something exists that CHP does not expect.

    UNKNOWN

    Unable to determine state.

    ERROR

    The check itself failed.


    5. Example

    CHP says:

    PHP = 8.3

    Actual:

    PHP = 8.2

    Result:

    DRIFT

    6. Missing Example

    CHP says:

    SSL = ACTIVE

    but the certificate is gone.

    Result:

    MISSING

    7. Unexpected Example

    CHP knows:

    example.com
    www.example.com

    but Nginx contains:

    test.example.com

    Result:

    UNEXPECTED

    This can identify configuration that was manually added outside CHP.


    8. Unknown Example

    Suppose DNS cannot be queried:

    DNS:
    UNKNOWN

    Do not report:

    DRIFT

    because you don’t know the actual state.


    9. Load Libraries

    The command should load:

    source /etc/cresignsys/hosting.conf
    source /etc/cresignsys/lib/common.sh
    source /etc/cresignsys/lib/logging.sh
    source /etc/cresignsys/lib/domain.sh
    source /etc/cresignsys/lib/database.sh
    source /etc/cresignsys/lib/discovery.sh

    10. Root Requirement

    Initially:

    require_root

    because reconciliation may need to inspect:

    /etc/nginx
    /etc/letsencrypt
    /run/php

    and protected site information.


    11. Find the Site

    The first database lookup is:

    domain
       ↓
    domains
       ↓
    site_id
       ↓
    sites

    Conceptually:

    SELECT site_id
    FROM domains
    WHERE domain = ?;

    12. Unknown Site

    If the domain isn’t registered in CHP:

    CresignSys Reconciliation
    =========================
    
    Domain:
    example.com
    
    Result:
    UNKNOWN
    
    Reason:
    Site is not registered in CHP database.

    Do not automatically import it.

    Use:

    hosting-db-import example.com

    instead.


    13. Read Site Metadata

    Retrieve:

    primary_domain
    web_root
    php_version
    site_user
    application_type
    management_state
    service_state
    health_state

    These become the expected values.


    14. Web Root Check

    Expected:

    /storage/websites/example.com/public

    Actual:

    [[ -d "$WEB_ROOT_PATH" ]]

    Results:

    MATCH

    or:

    MISSING

    15. Web Root Drift

    Suppose database says:

    /storage/websites/example.com/public

    but actual site is now:

    /storage/websites/example.com/site

    Result:

    DRIFT

    Don’t automatically change the database during reconciliation.


    16. Owner Check

    Expected:

    www-data:www-data

    Actual:

    stat -c '%U:%G' "$WEB_ROOT_PATH"

    If they differ:

    DRIFT

    Example:

    Owner:
    Expected: www-data:www-data
    Actual:   root:root
    Result:   DRIFT

    17. Why Owner Drift Matters

    A website accidentally owned by:

    root:root

    may cause:

    WordPress updates fail
    uploads fail
    cache writes fail

    while ownership that is too permissive can create security problems.

    Reconciliation should identify this, not silently modify it.


    18. Application Check

    Database:

    WORDPRESS

    Actual:

    wp-config.php
    wp-content/
    wp-admin/

    Result:

    MATCH

    If WordPress was removed:

    MISSING

    If the site was changed to another application:

    DRIFT

    19. WordPress Version

    If the database stores:

    WordPress:
    6.8.2

    and actual:

    6.8.3

    this may be:

    DRIFT

    but be careful.

    Version differences aren’t necessarily errors.

    A future model should distinguish:

    DESIRED VERSION

    from:

    OBSERVED VERSION

    20. Configuration vs Runtime

    This distinction is important.

    Some properties are:

    CONFIGURATION

    while others are:

    RUNTIME HEALTH

    For example:

    PHP version = configuration
    PHP-FPM running = runtime

    Therefore:

    PHP version:
    MATCH
    
    PHP-FPM:
    FAIL

    is perfectly possible.


    21. Nginx Check

    First verify:

    systemctl is-active --quiet nginx

    Then inspect the configuration.

    Expected:

    example.com

    Actual:

    server_name example.com www.example.com;

    22. Nginx Service Result

    Possible:

    OK
    FAIL
    UNKNOWN

    For example:

    Nginx Service:
    OK

    But the configuration itself may still have drift.


    23. Nginx Configuration Test

    Run:

    nginx -t

    If valid:

    Nginx Configuration:
    VALID

    If invalid:

    Nginx Configuration:
    ERROR

    This should be reported independently of whether the Nginx service is currently running.


    24. Nginx Domain Check

    Compare expected domains from:

    domains

    with actual server_name entries.

    Example:

    EXPECTED
    --------
    example.com
    www.example.com
    
    ACTUAL
    ------
    example.com
    www.example.com
    old.example.com

    Result:

    UNEXPECTED:
    old.example.com

    25. Don’t Treat Every Nginx Server Name as a Site

    Nginx may contain:

    default_server
    localhost
    internal services
    monitoring endpoints

    The reconciliation engine must identify the specific server block associated with the CHP site’s configuration.

    Don’t compare the entire Nginx installation against one website.


    26. PHP-FPM Version

    Expected:

    8.3

    Actual from Nginx:

    php8.3-fpm.sock

    Result:

    MATCH

    If:

    php8.2-fpm.sock

    then:

    DRIFT

    27. PHP-FPM Runtime

    Check:

    systemctl is-active php8.3-fpm

    Result:

    PHP-FPM:
    OK

    If stopped:

    PHP-FPM:
    FAIL

    28. PHP Socket

    Check:

    test -S /run/php/php8.3-fpm.sock

    Possible:

    MATCH
    MISSING

    If the service is active but the expected socket doesn’t exist:

    PHP Socket:
    MISSING

    29. MySQL

    For WordPress sites, verify the configured database connection.

    Use:

    wp db check

    If successful:

    Database:
    OK

    If not:

    Database:
    FAIL

    Don’t alter the database.


    30. SSL Check

    Expected from CHP:

    SSL:
    ACTIVE

    Actual:

    certificate exists
    certificate valid
    certificate covers domain

    Then:

    MATCH

    31. SSL Expiry

    A certificate can exist but be close to expiration.

    For example:

    SSL:
    ACTIVE
    
    Expires:
    2026-08-20
    
    Days remaining:
    7

    Report:

    WARNING

    rather than:

    MATCH

    if your policy considers that important.


    32. SSL Domain Coverage

    Expected:

    example.com
    www.example.com

    Certificate SANs:

    example.com
    www.example.com

    Result:

    MATCH

    If the certificate only contains:

    example.com

    then:

    DRIFT

    for www.example.com.


    33. DNS Check

    Expected server IP:

    SERVER_IPV4

    Actual:

    dig +short A example.com

    If equal:

    MATCH

    If different:

    DRIFT

    34. Multiple DNS Addresses

    Suppose:

    example.com

    returns:

    203.0.113.10
    203.0.113.11

    but CHP expects only:

    203.0.113.10

    Don’t immediately say:

    WRONG

    because multiple A records may be intentional.

    Use:

    MULTIPLE

    or:

    DRIFT

    depending on the configured hosting policy.


    35. DNS Failure

    If DNS cannot be queried:

    DNS:
    UNKNOWN

    not:

    DRIFT

    This distinction is important for reliable monitoring.


    36. Backup Check

    CHP database says:

    12 backups

    Actual directory contains:

    12

    Result:

    MATCH

    If database says:

    12

    but disk contains:

    8

    then:

    DRIFT

    37. Backup Verification

    A backup should ideally have:

    DISCOVERED
    VERIFIED

    state.

    Reconciliation can report:

    Backups:
    12 expected
    
    Verified:
    10
    
    Unverified:
    2

    Overall:

    WARNING

    rather than automatically calling the site broken.


    38. Overall Site Result

    Use priority:

    ERROR
      ↓
    DRIFT
      ↓
    MISSING
      ↓
    UNEXPECTED
      ↓
    WARNING
      ↓
    MATCH

    A site with:

    DNS MATCH
    SSL MATCH
    Nginx MATCH
    PHP DRIFT

    should have:

    OVERALL:
    DRIFT

    39. Example Output

    CresignSys Reconciliation
    =========================
    
    Site:
    example.com
    
    Management:
    MANAGED
    
    Filesystem
    ----------
    Web Root       MATCH
    Owner          MATCH
    Application    MATCH
    
    Nginx
    -----
    Service        OK
    Configuration  MATCH
    Domains        MATCH
    
    PHP
    ---
    Version        DRIFT
    Expected       8.3
    Actual         8.2
    Service        OK
    
    MySQL
    -----
    Connection     OK
    
    SSL
    ---
    Certificate    MATCH
    Domains        MATCH
    Expiry         WARNING
    
    DNS
    ---
    A Record       MATCH
    
    Backups
    -------
    Inventory      MATCH
    Verification   WARNING
    
    Overall:
    DRIFT

    40. Don’t Repair Automatically

    Even though CHP knows:

    PHP expected 8.3
    PHP actual 8.2

    do not execute:

    apt install php8.3

    or change Nginx.

    The command is only reporting the difference.


    41. JSON Output

    Add:

    sudo hosting-reconcile example.com --json

    Example:

    {
      "domain": "example.com",
      "overall": "DRIFT",
      "checks": {
        "web_root": "MATCH",
        "owner": "MATCH",
        "nginx": "MATCH",
        "php_version": "DRIFT",
        "php_service": "OK",
        "mysql": "OK",
        "ssl": "MATCH",
        "dns": "MATCH",
        "backups": "WARNING"
      }
    }

    This will later be extremely useful for the web control panel.


    42. Why JSON Matters

    The future dashboard can request:

    GET /sites/example.com/status

    and receive:

    {
      "overall": "DRIFT"
    }

    The dashboard doesn’t need to parse:

    PHP:
    DRIFT

    from human-readable terminal output.


    43. Exit Codes

    Use meaningful exit codes.

    For example:

    0 = MATCH
    1 = DRIFT
    2 = ERROR
    3 = UNKNOWN

    Then:

    hosting-reconcile example.com
    echo $?

    can be used by automation.


    44. Monitoring

    A future monitoring process could execute:

    hosting-reconcile example.com

    and understand:

    0 → healthy
    1 → investigate
    2 → checker failure

    without parsing text.


    45. Operation Logging

    Every reconciliation should create an operation:

    operation:
    RECONCILE

    For example:

    site_id:
    7
    
    status:
    SUCCESS
    
    message:
    DRIFT_DETECTED

    Be careful with terminology:

    A reconciliation operation can execute successfully even if it discovers drift.

    For example:

    operation status = SUCCESS
    reconciliation result = DRIFT

    These are different concepts.


    46. Important Distinction

    Don’t write:

    RECONCILE
    FAILED

    just because PHP differs.

    The reconciliation process succeeded.

    It discovered:

    DRIFT

    So:

    Operation:
    SUCCESS
    
    Result:
    DRIFT

    is better.


    47. Database Fields

    Eventually operations should include:

    result

    For example:

    ALTER TABLE operations
    ADD COLUMN result TEXT;

    Then:

    operation = RECONCILE
    status = SUCCESS
    result = DRIFT

    48. Reconciliation History

    This lets you answer:

    When did the site first become unhealthy?

    For example:

    Aug 13 14:00  MATCH
    Aug 13 15:00  MATCH
    Aug 13 16:00  DRIFT
    Aug 13 17:00  DRIFT

    This becomes valuable for troubleshooting.


    49. Don’t Run Too Frequently Yet

    For manual testing:

    hosting-reconcile example.com

    is enough.

    Later, scheduled monitoring can run every:

    15 minutes
    30 minutes
    1 hour

    depending on the monitoring design.

    For a first version, don’t build a daemon yet.


    50. Reconciliation Library

    Create:

    sudo nano /etc/cresignsys/lib/reconcile.sh

    Functions:

    reconcile_web_root
    reconcile_owner
    reconcile_application
    reconcile_nginx
    reconcile_php
    reconcile_mysql
    reconcile_ssl
    reconcile_dns
    reconcile_backups
    reconcile_site

    51. Standard Function Result

    Each function should conceptually return:

    MATCH
    DRIFT
    MISSING
    UNEXPECTED
    UNKNOWN
    ERROR

    For example:

    reconcile_web_root "$DOMAIN"

    returns:

    MATCH

    or:

    MISSING

    52. Don’t Mix Display With Logic

    Avoid writing:

    reconcile_php() {
        echo "PHP: DRIFT"
    }

    Instead:

    result="$(reconcile_php "$DOMAIN")"

    Then the presentation layer decides:

    PHP:
    DRIFT

    This allows:

    CLI
    JSON
    API
    Web dashboard

    to use the same underlying logic.


    53. Internal Result Structure

    A future function can return:

    {
      "check": "php_version",
      "result": "DRIFT",
      "expected": "8.3",
      "actual": "8.2"
    }

    The first Bash implementation can use simpler variables.


    54. Expected/Actual Is Essential

    For every meaningful drift, show:

    EXPECTED
    ACTUAL

    Example:

    PHP Version
    
    Expected:
    8.3
    
    Actual:
    8.2
    
    Result:
    DRIFT

    This is much more useful than:

    PHP:
    FAIL

    55. Example DNS Drift

    DNS A Record
    
    Expected:
    YOUR_SERVER_IP
    
    Actual:
    ANOTHER_IP
    
    Result:
    DRIFT

    Don’t automatically modify DNS.


    56. Example Ownership Drift

    Filesystem Owner
    
    Expected:
    www-data:www-data
    
    Actual:
    root:root
    
    Result:
    DRIFT

    Again, report only.


    57. Example SSL Drift

    SSL Certificate
    
    Expected:
    example.com
    www.example.com
    
    Actual:
    example.com
    
    Result:
    DRIFT

    This immediately tells the operator what changed.


    58. Example Missing Nginx Configuration

    Nginx Configuration
    
    Expected:
    Detected
    
    Actual:
    Not found
    
    Result:
    MISSING

    59. Reconciliation Categories

    A useful final report structure is:

    FILESYSTEM
    NGINX
    PHP
    DATABASE
    WORDPRESS
    SSL
    DNS
    BACKUPS

    This gives the operator a predictable interface.


    60. Full Reconciliation Flow

    hosting-reconcile example.com
                  │
                  ▼
            Load CHP record
                  │
                  ▼
            Acquire site lock
                  │
                  ▼
            Read actual state
                  │
          ┌───────┼────────┐
          ▼       ▼        ▼
       Files    Nginx    Services
          │       │        │
          └───────┼────────┘
                  ▼
              DNS / SSL
                  │
                  ▼
              Database
                  │
                  ▼
              WordPress
                  │
                  ▼
               Backups
                  │
                  ▼
            Calculate result
                  │
                  ▼
            Display / JSON
                  │
                  ▼
              Audit log

    61. Site Lock

    Use the lock created in Lesson 077:

    /var/lock/cresignsys/example.com.lock

    Why lock a read-only reconciliation?

    Because another operation could be changing the site’s configuration simultaneously.

    For example:

    REPAIR
       │
       ├── changing Nginx
       │
       └── changing PHP
    
    RECONCILE
       │
       └── reading Nginx/PHP

    Without a lock, reconciliation might capture a temporary intermediate state.


    62. Don’t Hold the Lock Forever

    The reconciliation operation should:

    acquire
     ↓
    collect
     ↓
    release

    rather than holding the lock during unrelated long-running operations.


    63. Concurrency Example

    Without locking:

    19:00:00 REPAIR starts
    19:00:01 Nginx config temporary
    19:00:02 RECONCILE reads
    19:00:03 REPAIR completes

    Reconciliation could incorrectly report:

    Nginx:
    DRIFT

    even though repair was still in progress.

    Locking prevents this ambiguity.


    64. Global vs Site Lock

    Use:

    site lock

    for:

    reconcile example.com
    repair example.com
    backup example.com
    restore example.com

    Use:

    global lock

    for:

    database migration
    CHP database restore
    global platform configuration

    65. Reconciliation Should Be Safe to Repeat

    Running:

    hosting-reconcile example.com

    10 times should not change the website.

    It only observes.

    This is called idempotent read-only behavior.


    66. Example Repeated Results

    Run 1:
    MATCH
    
    Run 2:
    MATCH
    
    Run 3:
    MATCH

    No server modification occurs.


    67. First Test

    After creating the command:

    sudo chmod +x /usr/local/bin/hosting-reconcile

    Run:

    sudo hosting-reconcile learn.cresignsys.com

    68. Expected Healthy Result

    For a healthy site:

    CresignSys Reconciliation
    ==========================
    
    Domain:
    learn.cresignsys.com
    
    Filesystem:
      Web Root       MATCH
      Owner          MATCH
      Application    MATCH
    
    Nginx:
      Service        MATCH
      Configuration  MATCH
      Domains        MATCH
    
    PHP:
      Version        MATCH
      Service        MATCH
      Socket         MATCH
    
    Database:
      Connection     MATCH
    
    WordPress:
      Installation   MATCH
    
    SSL:
      Certificate    MATCH
      Domains        MATCH
      Expiry         MATCH
    
    DNS:
      A Record       MATCH
    
    Backups:
      Inventory      MATCH
    
    Overall:
    MATCH

    69. If There Is Drift

    Example:

    PHP:
      Version        DRIFT
      Expected       8.3
      Actual         8.2
    
    Overall:
    DRIFT

    The correct next step is not:

    hosting-repair

    automatically.

    First investigate why the difference exists.


    70. Why Manual Investigation Comes First

    Possible reasons:

    1. Administrator changed PHP manually
    2. CHP database is outdated
    3. Import detected the wrong PHP socket
    4. Site intentionally uses another PHP version
    5. Nginx configuration changed

    The reconciliation engine identifies the difference.

    It does not assume the cause.


    71. Reconciliation vs Repair

    Keep these commands separate:

    hosting-reconcile
           │
           └── READ
    
    hosting-repair
           │
           └── WRITE

    This separation is one of the most important CHP design decisions.


    72. Future Repair Architecture

    Eventually:

    RECONCILE
       ↓
    DRIFT
       ↓
    OPERATOR CONFIRMATION
       ↓
    REPAIR PLAN
       ↓
    VALIDATE PLAN
       ↓
    APPLY
       ↓
    VERIFY
       ↓
    RECONCILE AGAIN

    Not:

    DRIFT
     ↓
    AUTOMATIC CHANGE

    at least not initially.


    73. Repair Plan

    A future command might produce:

    CresignSys Repair Plan
    ======================
    
    Site:
    example.com
    
    Detected:
    PHP version drift
    
    Expected:
    8.3
    
    Actual:
    8.2
    
    Proposed action:
    Update site Nginx PHP-FPM configuration to 8.3
    
    Risk:
    MEDIUM
    
    Changes:
    1. Nginx configuration
    2. Nginx reload
    
    Rollback:
    Available
    
    Apply:
    hosting-repair example.com

    This is much safer.


    74. The Long-Term CHP Loop

    We are building toward:

              DESIRED STATE
                    │
                    ▼
              RECONCILIATION
                    │
                    ▼
              ACTUAL STATE
                    │
                    ▼
                 DRIFT?
                /      \
              NO        YES
              │          │
              ▼          ▼
           HEALTHY   REPAIR PLAN
                         │
                         ▼
                      APPROVE
                         │
                         ▼
                       APPLY
                         │
                         ▼
                     VERIFY
                         │
                         ▼
                  RECONCILIATION

    This is the core operating model of CHP.


    75. Lesson 081 — Core Principle

    hosting-reconcile is the bridge between:

    CHP DATABASE

    and:

    REAL SERVER

    It should answer precisely:

    What does CHP expect?
    What actually exists?
    Do they match?
    If not, exactly where do they differ?

    It should not change anything.


    Next Lesson — 082

    Build the CHP Health Engine

    Reconciliation tells us about configuration drift.

    Health monitoring answers a different question:

    Is the website actually working right now?

    We will build:

    sudo hosting-health example.com

    to test:

    DNS
    HTTP
    HTTPS
    TLS
    Nginx
    PHP-FPM
    MySQL
    WordPress
    response time
    HTTP status
    redirects
    disk space
    memory
    CPU/load

    The architecture will become:

                     CHP
                      │
            ┌─────────┴─────────┐
            ▼                   ▼
     RECONCILIATION           HEALTH
            │                   │
       "Is it configured       "Is it
         correctly?"            working?"
            │                   │
            └─────────┬─────────┘
                      ▼
                 SITE STATUS

    This distinction will allow CHP to tell the difference between configuration drift and an actual service outage.

  • CresignSys Learn — Lesson 080

    Implement hosting-db-import

    This lesson turns the previous design into an actual migration workflow.

    The command will be:

    sudo hosting-db-import example.com

    and the safest first version will support:

    sudo hosting-db-import example.com --dry-run
    sudo hosting-db-import example.com
    sudo hosting-db-import example.com --refresh

    The importer will not modify the website.


    1. Import Pipeline

    hosting-db-import
            │
            ▼
    Validate domain
            │
            ▼
    Locate website
            │
            ▼
    Discover metadata
            │
            ├── Files
            ├── Owner
            ├── WordPress
            ├── Database
            ├── PHP-FPM
            ├── Nginx
            ├── DNS
            ├── SSL
            └── Backup
            │
            ▼
    Discovery report
            │
            ▼
    Validation
            │
            ▼
    Database transaction
            │
            ▼
    Verify
            │
            ▼
    VERIFIED

    2. First Create the Database

    Before importing sites, make sure the CHP database exists.

    sudo hosting-db-init

    Then verify:

    sqlite3 /var/lib/cresignsys/chp.db ".tables"

    You should see tables such as:

    backups
    certificate_domains
    domains
    operations
    schema_migrations
    services
    sites
    ssl_certificates

    3. Create Discovery Library

    Create:

    sudo nano /etc/cresignsys/lib/discovery.sh

    This library will contain read-only discovery functions.

    Structure:

    discovery.sh
    ├── discover_web_root
    ├── discover_owner
    ├── discover_application
    ├── discover_wordpress
    ├── discover_database
    ├── discover_php
    ├── discover_nginx
    ├── discover_dns
    ├── discover_ssl
    └── discover_backups

    4. Discovery Must Be Read-Only

    The discovery functions must only perform operations such as:

    stat
    find
    grep
    awk
    sed
    dig
    openssl
    wp
    mysql
    nginx -T

    They must not perform:

    mkdir
    rm
    mv
    cp
    systemctl restart
    systemctl reload
    wp update
    mysql ALTER

    5. discover_web_root

    Use the CHP convention first:

    discover_web_root() {
        local domain="$1"
    
        local root="${WEB_ROOT}/${domain}/public"
    
        if [[ -d "$root" ]]; then
            printf '%s\n' "$root"
            return 0
        fi
    
        return 1
    }

    6. Existing Layouts

    Don’t assume every existing site uses:

    /storage/websites/domain/public

    A future discovery system can search:

    /storage/websites/domain/public
    /storage/websites/domain
    /var/www/domain/public
    /var/www/domain

    But for the first version, use your current CHP convention.

    This reduces the chance of accidentally discovering an unrelated directory.


    7. discover_owner

    discover_owner() {
        local root="$1"
    
        stat -c '%U:%G' "$root"
    }

    Example:

    www-data:www-data

    8. discover_application

    Start with:

    discover_application() {
        local root="$1"
    
        if [[ -f "${root}/wp-config.php" ]] &&
           [[ -d "${root}/wp-admin" ]]; then
            printf 'WORDPRESS\n'
        elif [[ -f "${root}/index.html" ]]; then
            printf 'STATIC\n'
        elif [[ -f "${root}/index.php" ]]; then
            printf 'PHP\n'
        else
            printf 'UNKNOWN\n'
        fi
    }

    This is intentionally simple.

    Later the application detector can become more sophisticated.


    9. WordPress Detection

    discover_wordpress() {
        local root="$1"
    
        [[ -f "${root}/wp-config.php" ]] &&
        [[ -d "${root}/wp-content" ]]
    }

    Then:

    if discover_wordpress "$WEB_ROOT_PATH"; then
        WORDPRESS="YES"
    else
        WORDPRESS="NO"
    fi

    10. WordPress Version

    If detected:

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT_PATH" core version

    But first verify that:

    command -v wp

    exists.

    If WP-CLI isn’t available:

    WordPress:
    DETECTED
    
    Version:
    UNKNOWN

    Don’t fail the entire import simply because WP-CLI isn’t installed.


    11. WordPress Database Discovery

    Use:

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT_PATH" config get DB_NAME

    Then:

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT_PATH" config get DB_HOST

    This is preferable to manually parsing wp-config.php because WordPress configuration can contain PHP expressions.


    12. Database Password

    Don’t retrieve:

    DB_PASSWORD

    unless absolutely required for a specific read-only verification.

    The importer should avoid placing credentials in:

    logs
    terminal output
    CHP database

    13. Database Verification

    Run:

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT_PATH" db check

    If successful:

    Database:
    OK

    If unsuccessful:

    Database:
    FAIL

    Still continue discovery.


    14. PHP-FPM Discovery

    The preferred source is the Nginx configuration.

    Find:

    fastcgi_pass

    For example:

    fastcgi_pass unix:/run/php/php8.3-fpm.sock;

    Extract:

    8.3

    or:

    /run/php/php8.3-fpm.sock

    15. Don’t Use Global PHP Version

    Avoid:

    php -v

    as the website PHP version.

    It can differ from the PHP-FPM version serving the site.

    Correct model:

    Nginx
     ↓
    PHP-FPM socket
     ↓
    PHP version

    16. PHP-FPM Verification

    Once the socket is discovered:

    test -S "$PHP_SOCKET"

    If true:

    PHP socket:
    OK

    Then determine the corresponding service:

    php8.3-fpm

    and check:

    systemctl is-active php8.3-fpm

    17. Nginx Discovery

    The safest source is:

    nginx -T

    but this produces a large amount of output.

    Capture it once:

    NGINX_DUMP="$(nginx -T 2>&1)"

    Then use the captured output for the discovery process.

    Don’t repeatedly execute:

    nginx -T

    for every field.


    18. Search for server_name

    You need to locate:

    server_name example.com www.example.com;

    and then associate it with:

    root /storage/websites/example.com/public;

    and:

    fastcgi_pass ...

    This creates a relationship:

    DOMAIN
      ↓
    SERVER BLOCK
      ├── root
      ├── PHP socket
      ├── SSL
      └── redirects

    19. Nginx Configuration Path

    Also determine the actual configuration file if possible.

    For example:

    /etc/nginx/sites-enabled/example.com.conf

    Store the path:

    nginx_config_path

    in CHP metadata.

    Don’t copy the entire configuration into SQLite.


    20. DNS Discovery

    Use:

    dig +short A "$DOMAIN"

    and:

    dig +short AAAA "$DOMAIN"

    Then compare with:

    SERVER_IPV4
    SERVER_IPV6

    Possible states:

    CORRECT
    PENDING
    WRONG
    MULTIPLE
    UNKNOWN

    21. CNAME Discovery

    dig +short CNAME "$DOMAIN"

    If a CNAME exists:

    CNAME:
    example.com.

    The importer should resolve the target and determine the final destination.


    22. Nameserver Discovery

    dig +short NS "$DOMAIN"

    Store this in the discovery result if useful.

    You don’t necessarily need to store every nameserver permanently in the domains table.

    A future DNS-history table could do that.


    23. SSL Discovery

    First look for certificate configuration.

    For Let’s Encrypt:

    /etc/letsencrypt/live/example.com/

    Check:

    fullchain.pem
    privkey.pem

    Do not copy:

    privkey.pem

    into the CHP database.


    24. Certificate Information

    Read:

    openssl x509 \
        -in /etc/letsencrypt/live/example.com/fullchain.pem \
        -noout \
        -issuer \
        -dates \
        -subject

    This gives information such as:

    issuer
    notBefore
    notAfter
    subject

    25. Certificate SANs

    Check:

    openssl x509 \
        -in /etc/letsencrypt/live/example.com/fullchain.pem \
        -noout \
        -ext subjectAltName

    This may show:

    DNS:example.com
    DNS:www.example.com

    Those domains can be mapped to the certificate.


    26. SSL State

    Calculate:

    ACTIVE
    EXPIRING_SOON
    EXPIRED
    MISSING
    INVALID

    For example:

    Certificate:
    ACTIVE
    
    Expires:
    2026-11-20

    27. Backup Discovery

    Look under:

    /backup/cresignsys/example.com/

    If the directory exists:

    Backup:
    DETECTED

    List backup objects.

    Don’t assume every file is valid.


    28. Backup Metadata

    For each backup:

    path
    timestamp
    size
    type

    Initially:

    status=DISCOVERED

    Later:

    hosting-backup-verify

    can change it to:

    VERIFIED

    29. Build Discovery Object

    Bash is not ideal for complex objects.

    For the first implementation, use variables:

    SITE_DOMAIN=""
    WEB_ROOT_PATH=""
    SITE_USER=""
    SITE_GROUP=""
    APPLICATION_TYPE=""
    PHP_VERSION=""
    PHP_SOCKET=""
    NGINX_CONFIG=""
    DATABASE_NAME=""
    DATABASE_HOST=""
    DNS_STATE=""
    SSL_STATE=""
    CERTIFICATE_PATH=""

    This keeps the initial importer understandable.


    30. Better Future Representation

    Eventually the discovery engine can output JSON:

    {
      "domain": "example.com",
      "web_root": "/storage/websites/example.com/public",
      "owner": "www-data:www-data",
      "application": "WORDPRESS",
      "php_version": "8.3",
      "php_socket": "/run/php/php8.3-fpm.sock",
      "dns_state": "CORRECT",
      "ssl_state": "ACTIVE"
    }

    Then the database importer consumes the JSON.

    Don’t build that complexity until the basic importer works.


    31. Create the Importer

    sudo nano /usr/local/bin/hosting-db-import

    Start:

    #!/usr/bin/env bash
    
    set -Eeuo pipefail

    32. Parse Options

    Support:

    DOMAIN
    --dry-run
    --refresh

    Example:

    sudo hosting-db-import example.com --dry-run

    Don’t use a large argument parser yet.

    A simple loop is enough.


    33. Validate Domain

    source /etc/cresignsys/hosting.conf
    source /etc/cresignsys/lib/common.sh
    source /etc/cresignsys/lib/domain.sh
    source /etc/cresignsys/lib/database.sh
    source /etc/cresignsys/lib/discovery.sh
    
    require_root
    
    DOMAIN="$(normalize_domain "$DOMAIN")"
    validate_domain "$DOMAIN"

    34. Check Existing Database Record

    Before importing:

    SELECT id FROM sites WHERE primary_domain = ?

    If it exists:

    Site already imported.

    Then:

    normal mode → stop
    --refresh → rediscover

    35. Discovery

    Run:

    discover_web_root
    discover_owner
    discover_application
    discover_wordpress
    discover_database
    discover_php
    discover_nginx
    discover_dns
    discover_ssl
    discover_backups

    No writes to the website.


    36. Display Discovery Report

    Example:

    CresignSys Site Discovery
    =========================
    
    Domain:
    learn.cresignsys.com
    
    Web Root:
     /storage/websites/learn.cresignsys.com/public
    
    Owner:
     www-data:www-data
    
    Application:
     WORDPRESS
    
    WordPress:
     DETECTED
    
    PHP:
     8.3
    
    PHP-FPM:
     OK
    
    Nginx:
     DETECTED
    
    DNS:
     CORRECT
    
    SSL:
     ACTIVE
    
    Database:
     DETECTED
    
    Backups:
     12 found

    37. Discovery Validation

    Before importing, verify minimum requirements:

    domain found
    web root found
    site is recognizable

    For example:

    [[ -d "$WEB_ROOT_PATH" ]] ||
        die "Web root not found"

    38. Don’t Require Every Component

    A static site may not have:

    PHP
    MySQL
    WordPress

    Therefore:

    PHP = NOT_APPLICABLE
    Database = NOT_APPLICABLE

    should be valid states.


    39. Application-Aware Discovery

    For WordPress:

    WordPress → expected
    PHP → expected
    Database → expected

    For static:

    WordPress → N/A
    PHP → N/A
    Database → N/A

    This prevents false errors.


    40. Database Transaction

    After discovery:

    BEGIN;

    Then insert site:

    INSERT INTO sites (
        primary_domain,
        web_root,
        php_version,
        site_user,
        service_state,
        health_state,
        created_at,
        updated_at
    )
    VALUES (...);

    Use the schema version you actually implemented, including management_state and application_type if those columns have already been migrated.


    41. Retrieve Site ID

    After insertion:

    SELECT last_insert_rowid();

    Store:

    SITE_ID

    For example:

    SITE_ID=7

    42. Insert Primary Domain

    INSERT INTO domains (
        site_id,
        domain,
        domain_type,
        dns_state,
        ssl_state,
        created_at,
        updated_at
    )
    VALUES (...);

    43. Insert Services

    For WordPress:

    NGINX
    PHP_FPM
    MYSQL
    WORDPRESS

    For a static site:

    NGINX

    only, unless additional services are discovered.


    44. Insert SSL

    If a certificate exists:

    ssl_certificates

    record:

    site_id
    certificate_path
    issuer
    expires_at
    status

    Then map:

    certificate_domains

    to the discovered domains.


    45. Insert Backups

    For each discovered backup:

    backups

    record:

    site_id
    backup_path
    backup_type
    size_bytes
    status
    created_at

    Use:

    DISCOVERED

    until verified.


    46. Insert Operation

    Create:

    operations

    record:

    operation=IMPORT
    status=RUNNING
    started_at=...

    After successful import:

    status=SUCCESS
    finished_at=...

    If something fails:

    status=FAILED
    message=...

    47. Transaction Failure

    If database insertion fails:

    ROLLBACK;

    The existing website remains untouched.

    That is the major safety benefit.


    48. Verify After Commit

    After:

    COMMIT;

    perform read-only checks:

    site exists
    domain exists
    services exist
    SSL record correct
    backup records correct

    Then:

    management_state=VERIFIED

    if the schema supports that field.


    49. Verify Against Live Server

    Compare:

    Database:
    web_root=/storage/websites/example.com/public

    with:

    Actual:
    directory exists

    Compare:

    Database:
    PHP=8.3

    with:

    Actual:
    PHP socket=8.3

    Compare:

    Database:
    DNS=CORRECT

    with:

    Current DNS:
    CORRECT

    50. Import Result

    Successful:

    CresignSys Database Import
    ==========================
    
    Domain:
    learn.cresignsys.com
    
    Site ID:
    7
    
    [OK] Site imported
    [OK] Domain imported
    [OK] Services imported
    [OK] SSL imported
    [OK] Backups imported
    [OK] Live verification passed
    
    Management:
    VERIFIED
    
    No website files were modified.

    51. Dry Run

    With:

    sudo hosting-db-import learn.cresignsys.com --dry-run

    the final output should be:

    DRY RUN
    
    Discovery successful.
    
    No database changes made.
    No website changes made.

    52. Refresh

    If the site already exists:

    sudo hosting-db-import learn.cresignsys.com --refresh

    the process becomes:

    existing record
          ↓
    discover actual state
          ↓
    compare
          ↓
    update metadata
          ↓
    record REFRESH operation

    Still:

    No website modification.

    53. Detect Drift During Refresh

    Suppose CHP database says:

    PHP=8.3

    but actual site uses:

    PHP=8.2

    The refresh should report:

    DRIFT DETECTED
    
    PHP
    CHP:    8.3
    Actual: 8.2

    Then update metadata only if your refresh policy says actual state should replace the stored observation.


    54. Desired vs Observed Metadata

    This reveals an important improvement.

    Instead of only:

    php_version

    eventually use:

    desired_php_version
    observed_php_version

    For example:

    desired: 8.3
    observed: 8.2

    Then:

    hosting-reconcile

    can detect drift.


    55. Add Desired/Observed Model Later

    Don’t redesign the whole schema immediately.

    The conceptual model is:

                  CHP DATABASE
                       │
            ┌──────────┴──────────┐
            ▼                     ▼
         DESIRED               OBSERVED
            │                     │
            └──────────┬──────────┘
                       ▼
                  RECONCILIATION

    This will become important when automatic repair is introduced.


    56. Import Existing CHP Websites

    For your current server, start with:

    sudo hosting-db-import learn.cresignsys.com --dry-run

    Then:

    sudo hosting-db-import learn.cresignsys.com

    Then:

    sudo hosting-site-status learn.cresignsys.com

    Then:

    sudo hosting-info learn.cresignsys.com

    57. Verify No Files Changed

    Before and after import, you can compare:

    find /storage/websites/learn.cresignsys.com/public \
        -type f -printf '%p\n' | sort

    The importer should not change this list.

    For a more rigorous test, record checksums of important files before import and compare afterward.


    58. Verify Nginx

    Before import:

    sudo nginx -t

    After import:

    sudo nginx -t

    The result should remain:

    syntax is ok
    test is successful

    And the importer itself should not have reloaded Nginx.


    59. Verify WordPress

    Run:

    sudo -u www-data \
    wp --path=/storage/websites/learn.cresignsys.com/public core is-installed

    before and after.

    Expected:

    success

    60. Verify Database

    Run:

    sudo -u www-data \
    wp --path=/storage/websites/learn.cresignsys.com/public db check

    before and after.

    The importer must not change WordPress tables.


    61. Import Safety Checklist

    Before considering the importer production-ready:

    [ ] Domain validation
    [ ] Read-only discovery
    [ ] Dry-run
    [ ] Database transaction
    [ ] Rollback on DB failure
    [ ] No website modifications
    [ ] No Nginx reload
    [ ] No PHP restart
    [ ] No WordPress update
    [ ] No MySQL schema modification
    [ ] No credential logging
    [ ] Idempotent import
    [ ] Refresh mode
    [ ] Post-import verification
    [ ] Audit operation recorded

    62. Important Security Rule

    Never print:

    DB_PASSWORD
    PRIVATE_KEY
    API_TOKEN

    to:

    terminal
    operations.log
    errors.log
    audit.log

    If an exception or command output accidentally contains sensitive information, sanitize it before logging.


    63. Import Errors

    Use specific error codes.

    For example:

    SITE_NOT_FOUND
    WEB_ROOT_NOT_FOUND
    INVALID_DOMAIN
    WORDPRESS_DETECTION_FAILED
    DATABASE_UNAVAILABLE
    NGINX_NOT_FOUND
    SSL_DISCOVERY_FAILED
    DATABASE_IMPORT_FAILED
    VERIFICATION_FAILED

    This is better than:

    ERROR

    64. Example Failure

    CresignSys Database Import
    ==========================
    
    Domain:
    example.com
    
    [OK] Web root
    [OK] WordPress
    [OK] PHP-FPM
    [OK] Nginx
    [FAIL] Database verification
    
    Reason:
    WordPress database connection failed.
    
    Result:
    IMPORT NOT PERFORMED
    
    No website changes made.

    This is the correct behavior.


    65. Don’t Import Partially

    If a required database operation fails:

    BEGIN
     ↓
    INSERT SITE
     ↓
    INSERT DOMAIN
     ↓
    INSERT SERVICE
     ↓
    ERROR
     ↓
    ROLLBACK

    You should not end with:

    site exists
    domain missing

    66. Operation History

    After failure:

    operations

    should contain:

    IMPORT
    FAILED

    with a safe message:

    Database verification failed

    Do not put the actual database password or sensitive command output into the message.


    67. Importer Architecture

    The final structure is:

    hosting-db-import
           │
           ├── argument parser
           │
           ├── domain validation
           │
           ├── discovery.sh
           │      ├── filesystem
           │      ├── WordPress
           │      ├── database
           │      ├── PHP
           │      ├── Nginx
           │      ├── DNS
           │      ├── SSL
           │      └── backup
           │
           ├── discovery report
           │
           ├── validation
           │
           ├── database transaction
           │
           └── verification

    68. What We Have Achieved

    The server can now transition from:

    WEBSITES EXIST

    to:

    CHP KNOWS ABOUT THE WEBSITES

    without changing the existing sites.

    That gives us:

    Existing Websites
           ↓
    CHP Database
           ↓
    CHP Status
           ↓
    CHP Monitoring
           ↓
    Optional CHP Management

    69. Current CHP Architecture

                             CRESIGNSYS
                                 │
                    ┌────────────┴────────────┐
                    ▼                         ▼
              CONTROL PLANE               DATA PLANE
                    │                         │
                    ▼                         ▼
              CHP SQLite DB             Website Files
                    │                    WordPress
           ┌────────┼────────┐           MySQL
           ▼        ▼        ▼
         Sites   Domains  Backups
           │        │
           ▼        ▼
       Services    SSL
           │
           ▼
      Operations
           │
           ▼
      Reconciliation
           │
           ▼
      Site Status

    70. Lesson 080 — Core Principle

    The importer is not a provisioning tool.

    Its job is:

    DISCOVER
       ↓
    RECORD
       ↓
    VERIFY

    not:

    DISCOVER
       ↓
    CHANGE
       ↓
    REBUILD

    That separation is what makes migration safe.


    Next Lesson — 081

    Build hosting-reconcile

    Now that CHP has both:

    DESIRED/KNOWN STATE

    and:

    ACTUAL SERVER STATE

    we can build the reconciliation engine.

    Target:

    sudo hosting-reconcile example.com

    It will compare:

    CHP DATABASE
         │
         ├── Domain
         ├── Web root
         ├── PHP
         ├── Nginx
         ├── SSL
         ├── DNS
         ├── WordPress
         └── Backup
                │
                ▼
           ACTUAL SERVER

    and report:

    MATCH
    DRIFT
    MISSING
    UNEXPECTED
    UNKNOWN

    Most importantly, Lesson 081 should remain read-only initially. Only after reconciliation is reliable should CHP be allowed to automatically repair detected drift.

  • CresignSys Learn — Lesson 079

    Migrate Existing Websites into the CHP Database

    The next step is to bring your existing websites under CHP management without changing or breaking them.

    Your existing structure is similar to:

    /storage/websites/
    ├── cresignsys.com/
    │   └── public/
    ├── learn.cresignsys.com/
    │   └── public/
    ├── indianclassicalacupuncture.cresignsys.com/
    │   └── public/
    └── ...

    The migration command will be:

    sudo hosting-db-import example.com

    The most important rule:

    Import must be READ-ONLY first. It discovers the existing installation; it does not modify it.


    1. Migration Architecture

                      EXISTING SERVER
                           │
                           ▼
                    DISCOVERY ENGINE
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
           Files          Nginx       PHP-FPM
              │            │            │
              ▼            ▼            ▼
           WordPress     SSL          MySQL
              │            │            │
              └────────────┼────────────┘
                           ▼
                      CHP DATABASE
                           │
                           ▼
                        VERIFY
                           │
                           ▼
                     IMPORTED SITE

    2. Safety Rule

    hosting-db-import should initially perform:

    READ
    READ
    READ
    READ

    and only write to:

    /var/lib/cresignsys/chp.db

    It should not:

    modify Nginx
    restart PHP
    change permissions
    change WordPress
    change MySQL
    issue SSL
    delete files

    3. Why Read-Only Discovery?

    Suppose your existing site is already working:

    example.com
       ↓
    Nginx
       ↓
    PHP-FPM
       ↓
    WordPress

    We don’t want the migration tool to suddenly regenerate:

    Nginx

    and introduce a problem.

    Migration and provisioning are different operations.


    4. Migration States

    Add a site state:

    DISCOVERED

    Then:

    DISCOVERED
        ↓
    IMPORTED
        ↓
    VERIFIED
        ↓
    MANAGED

    For a new CHP-created website:

    PROVISIONING
        ↓
    ACTIVE

    5. Discovery vs Management

    This distinction is important.

    Discovered

    CHP knows:

    The website exists.

    Imported

    CHP has created database metadata.

    Verified

    CHP confirmed that the metadata matches the server.

    Managed

    CHP is authorized to modify the site configuration.

    This avoids accidentally treating every discovered website as fully managed.


    6. Add management_state

    Update the sites table.

    Add:

    ALTER TABLE sites
    ADD COLUMN management_state TEXT NOT NULL DEFAULT 'IMPORTED';

    Better states:

    DISCOVERED
    IMPORTED
    VERIFIED
    MANAGED

    For the initial migration, use:

    IMPORTED

    until verification succeeds.


    7. Migration Command

    Create:

    sudo nano /usr/local/bin/hosting-db-import

    Start with:

    #!/usr/bin/env bash
    
    set -Eeuo pipefail

    8. Load CHP Configuration

    source /etc/cresignsys/hosting.conf
    source /etc/cresignsys/lib/common.sh
    source /etc/cresignsys/lib/logging.sh
    source /etc/cresignsys/lib/domain.sh
    source /etc/cresignsys/lib/database.sh

    9. Require Root

    require_root

    10. Validate Argument

    if [[ $# -ne 1 ]]; then
        echo "Usage: hosting-db-import DOMAIN"
        exit 1
    fi
    
    DOMAIN="$(normalize_domain "$1")"
    
    validate_domain "$DOMAIN"

    11. Determine Expected Web Root

    Your CHP convention is:

    /storage/websites/DOMAIN/public

    Therefore initially:

    WEB_ROOT="${WEB_ROOT}/${DOMAIN}/public"

    But don’t assume this forever.

    Existing websites may have different layouts.


    12. Discovery Should Search First

    Check:

    if [[ -d "$WEB_ROOT" ]]; then
        echo "[OK] Web root found"
    else
        echo "[FAIL] Web root not found"
    fi

    For example:

    /storage/websites/example.com/public

    13. Don’t Create Missing Directories

    This command:

    hosting-db-import

    must never do:

    mkdir -p

    for a missing site.

    If the directory doesn’t exist:

    IMPORT FAILED

    not:

    DIRECTORY CREATED

    14. Discover Ownership

    Run:

    stat -c '%U:%G' "$WEB_ROOT"

    Example:

    www-data:www-data

    Store:

    site_user=www-data

    and:

    site_group=www-data

    If your CHP architecture uses one dedicated Linux user per site, record that instead.


    15. Discover WordPress

    Check:

    [[ -f "$WEB_ROOT/wp-config.php" ]]

    If present:

    WordPress:
    DETECTED

    Otherwise:

    WordPress:
    NOT DETECTED

    This matters because CHP should eventually support more than WordPress.


    16. Don’t Assume Every Site Is WordPress

    Your hosting platform may eventually host:

    WordPress
    Laravel
    PHP application
    Static HTML
    Node.js
    Python

    Therefore the database should eventually contain:

    application_type

    Possible values:

    WORDPRESS
    PHP
    STATIC
    LARAVEL
    NODE
    PYTHON
    CUSTOM
    UNKNOWN

    For the current migration:

    WORDPRESS

    if detected.


    17. Add Application Type

    Eventually:

    ALTER TABLE sites
    ADD COLUMN application_type TEXT NOT NULL DEFAULT 'UNKNOWN';

    Example:

    example.com
    application_type=WORDPRESS

    18. Discover WordPress Version

    If WordPress exists:

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT" core version

    Example:

    6.8.2

    Store:

    wordpress_version=6.8.2

    Don’t install or update anything during import.


    19. Discover WordPress Database

    You can use:

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT" config get DB_NAME

    Then:

    wp --path="$WEB_ROOT" config get DB_USER

    and:

    wp --path="$WEB_ROOT" config get DB_HOST

    20. Do Not Store Database Password in CHP Metadata

    This is important.

    Do not copy:

    DB_PASSWORD

    into:

    sites

    or:

    services

    The WordPress application already has its credentials.

    CHP only needs enough information to identify and verify the application.


    21. Database Metadata

    Store:

    database_name
    database_host

    if necessary.

    Do not store:

    database_password

    in the CHP database.


    22. MySQL Verification

    After discovering the database:

    wp --path="$WEB_ROOT" db check

    If successful:

    Database:
    OK

    If it fails:

    Database:
    ERROR

    But don’t modify the database.


    23. Discover PHP Version

    There are several possible sources.

    First inspect the Nginx configuration for the PHP-FPM socket:

    fastcgi_pass unix:/run/php/php8.3-fpm.sock;

    or:

    fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;

    Extract:

    8.3

    24. Don’t Guess PHP Version From CLI

    This:

    php -v

    only tells you the CLI PHP version.

    It may not be the version used by Nginx.

    For example:

    CLI:
    PHP 8.3
    
    Website:
    PHP 8.2

    Therefore CHP should discover the actual PHP-FPM configuration.


    25. Discover PHP-FPM Socket

    Search the site’s Nginx configuration.

    For example:

    grep -R "fastcgi_pass" /etc/nginx -n

    Then identify:

    example.com
          ↓
    PHP socket
          ↓
    php8.3-fpm

    26. Discover Nginx Configuration

    Search:

    sudo nginx -T

    and identify the server_name associated with:

    example.com

    The importer should record:

    nginx:
    DETECTED

    and ideally:

    nginx_config_path

    27. Don’t Store Arbitrary Nginx Configuration in the Database

    Avoid putting the complete Nginx configuration into:

    sites

    The database should store metadata such as:

    nginx_config_path

    The actual configuration remains on disk.


    28. Discover Domain Names

    The primary domain may be:

    example.com

    But Nginx may contain:

    server_name example.com www.example.com;

    The importer should discover:

    example.com
    www.example.com

    and compare them with the database.


    29. Domain Classification

    For the initial importer:

    example.com

    can become:

    PRIMARY

    and:

    www.example.com

    can become:

    ALIAS

    However, don’t automatically classify every secondary hostname as an alias if its Nginx behavior is different.

    Inspect redirects.


    30. Redirect Detection

    If:

    return 301 https://example.com$request_uri;

    exists for:

    www.example.com

    then its type might be:

    REDIRECT

    rather than:

    ALIAS

    31. DNS Discovery

    Run:

    dig +short A "$DOMAIN"

    and:

    dig +short AAAA "$DOMAIN"

    Then compare with:

    SERVER_IPV4
    SERVER_IPV6

    Set:

    dns_state

    to:

    CORRECT
    WRONG
    PENDING
    MULTIPLE
    UNKNOWN

    32. Don’t Change DNS

    Again:

    hosting-db-import

    does not create DNS records.

    It only discovers them.


    33. SSL Discovery

    Check whether the site has:

    /etc/letsencrypt/live/example.com/

    or another certificate configuration.

    Determine:

    certificate:
    FOUND

    Then inspect:

    issuer
    expiry
    domains

    34. Certificate Expiry

    For example:

    Expires:
    2026-10-12

    Calculate:

    days_remaining

    Then:

    > 30 days → ACTIVE
    7-30 days → EXPIRING_SOON
    < 7 days → WARNING
    expired → EXPIRED

    Use the actual date arithmetic rather than hard-coding a date.


    35. SSL Certificate Table

    The importer can create:

    ssl_certificates

    record:

    site_id=1
    certificate_path=/etc/letsencrypt/live/example.com/fullchain.pem
    private_key_path=/etc/letsencrypt/live/example.com/privkey.pem
    issuer=Let's Encrypt
    expires_at=...
    status=ACTIVE

    Again, the private key itself is not copied into the CHP database.


    36. Certificate Domain Mapping

    If the certificate contains:

    example.com
    www.example.com

    create:

    certificate_domains

    relationships.


    37. Discover Backup Configuration

    Check:

    /backup/cresignsys/example.com/

    if that is your CHP backup location.

    If backups exist:

    Backup:
    DETECTED

    Then import backup metadata.


    38. Don’t Assume Every Backup Is Valid

    A file existing doesn’t mean the backup is valid.

    Initially record:

    status=DISCOVERED

    Then:

    hosting-backup-verify

    can later change it to:

    VERIFIED

    39. Backup Discovery

    For each backup directory:

    2026-08-13_160000

    collect:

    path
    size
    timestamp
    type

    Do not perform a full restore during import.


    40. Discover Site State

    Determine:

    ACTIVE
    SUSPENDED
    UNKNOWN

    by inspecting CHP state/configuration if it already exists.

    If no state exists:

    management_state=IMPORTED
    service_state=UNKNOWN

    Do not guess.


    41. Discovery Report

    Before writing anything to the database, print:

    CresignSys Site Discovery
    =========================
    
    Domain:
    example.com
    
    Web Root:
     /storage/websites/example.com/public
    
    Owner:
     www-data:www-data
    
    Application:
     WordPress
    
    WordPress:
     6.8.2
    
    PHP-FPM:
     8.3
    
    Nginx:
     DETECTED
    
    DNS:
     CORRECT
    
    SSL:
     ACTIVE
    
    Database:
     wp_example
    
    Backups:
     12 found

    Then:

    Ready for import.

    42. Add --dry-run

    The command should support:

    sudo hosting-db-import example.com --dry-run

    This performs:

    DISCOVERY

    but doesn’t write to the database.


    43. Dry-Run Output

    CresignSys Database Import
    ==========================
    
    Mode:
    DRY RUN
    
    Site:
    example.com
    
    [OK] Web root
    [OK] WordPress
    [OK] PHP-FPM
    [OK] Nginx
    [OK] DNS
    [OK] SSL
    [OK] Database
    
    No database changes made.

    This is extremely useful.


    44. Normal Import

    Then:

    sudo hosting-db-import example.com

    performs:

    DISCOVER
       ↓
    VALIDATE
       ↓
    DATABASE TRANSACTION
       ↓
    CREATE SITE
       ↓
    CREATE DOMAINS
       ↓
    CREATE SERVICES
       ↓
    CREATE SSL
       ↓
    CREATE BACKUPS
       ↓
    COMMIT

    45. Database Transaction

    The import should use:

    BEGIN;

    Then:

    INSERT sites
    INSERT domains
    INSERT services
    INSERT SSL
    INSERT backups
    INSERT operation

    Finally:

    COMMIT;

    If something fails:

    ROLLBACK;

    46. Record the Import Operation

    Create:

    operations

    record:

    operation=IMPORT
    status=SUCCESS
    message=Existing site imported

    This gives you an audit trail.


    47. Idempotency

    If the site is already imported:

    sudo hosting-db-import example.com

    should not create a duplicate.

    Instead:

    Site already exists in CHP database.

    Then offer:

    Use --refresh to rediscover metadata.

    48. Add --refresh

    sudo hosting-db-import example.com --refresh

    means:

    Existing CHP record
           ↓
    Rediscover actual server state
           ↓
    Compare
           ↓
    Update metadata

    Still don’t modify the website.


    49. Refresh Is Not Repair

    This distinction is essential.

    hosting-db-import example.com --refresh

    means:

    Update CHP’s understanding of the site.

    While:

    hosting-repair example.com

    means:

    Change the server to restore the desired state.


    50. Import Safety Modes

    Eventually support:

    --dry-run
    --refresh
    --force

    But don’t implement --force yet.

    A safe first version only needs:

    default
    --dry-run
    --refresh

    51. Example Existing Site

    Suppose:

    /storage/websites/learn.cresignsys.com/public

    contains:

    wp-config.php
    wp-admin/
    wp-content/
    wp-includes/

    Nginx contains:

    server_name learn.cresignsys.com;

    and PHP uses:

    php8.3-fpm

    The importer discovers:

    Site:
    learn.cresignsys.com
    
    Application:
    WORDPRESS
    
    PHP:
    8.3
    
    Nginx:
    YES
    
    Database:
    YES
    
    SSL:
    YES

    52. Database Record

    The site could become:

    sites
    
    id: 1
    primary_domain: learn.cresignsys.com
    web_root: /storage/websites/learn.cresignsys.com/public
    php_version: 8.3
    site_user: www-data
    application_type: WORDPRESS
    management_state: VERIFIED
    service_state: ACTIVE
    health_state: HEALTHY

    53. Domain Record

    domains
    
    id: 1
    site_id: 1
    domain: learn.cresignsys.com
    domain_type: PRIMARY
    dns_state: CORRECT
    ssl_state: ACTIVE

    54. Service Records

    services
    
    site_id | service_name | status | version
    ------------------------------------------
    1       | NGINX        | OK     |
    1       | PHP_FPM      | OK     | 8.3
    1       | MYSQL        | OK     | 8.0
    1       | WORDPRESS    | OK     | 6.x

    55. Import Verification

    After database insertion, run a read-only verification:

    DATABASE
       ↓
    Expected:
    learn.cresignsys.com
       ↓
    LIVE SERVER
       ↓
    Actual:
    learn.cresignsys.com

    If they match:

    VERIFIED

    56. Management State

    Only after successful verification:

    management_state=VERIFIED

    Later, when you explicitly allow CHP to modify the site:

    management_state=MANAGED

    This is a valuable safety boundary.


    57. Why MANAGED Matters

    Suppose you import:

    existing-client-site.com

    You may want CHP to monitor it but not automatically modify it.

    Therefore:

    VERIFIED

    can mean:

    CHP understands the site.

    while:

    MANAGED

    means:

    CHP is authorized to control its configuration.


    58. Site Management Policy

    Add eventually:

    management_policy

    with:

    READ_ONLY
    MONITOR
    MANAGED

    For initial imports:

    READ_ONLY

    is safest.


    59. Example

    existing-site.com
    
    Management:
    READ_ONLY
    
    Health:
    HEALTHY

    CHP can show:

    DNS
    SSL
    PHP
    Nginx
    WordPress
    Backup

    but automatic repair is disabled.


    60. Explicit Adoption

    Later introduce:

    sudo hosting-adopt example.com

    This would mean:

    READ_ONLY
         ↓
    MANAGED

    after explicit confirmation.

    This is safer than automatically taking control of an existing website.


    61. Adoption Workflow

    hosting-adopt example.com
            ↓
    Show discovered configuration
            ↓
    Show what CHP will manage
            ↓
    Require confirmation
            ↓
    Create CHP templates/state
            ↓
    Set MANAGEMENT_POLICY=MANAGED

    62. What Adoption Should NOT Do Automatically

    It should not immediately:

    change DNS
    renew SSL
    upgrade PHP
    update WordPress
    change permissions
    delete unknown files

    Adoption means:

    CHP takes ownership of the site’s management metadata.

    Not:

    CHP changes everything.


    63. Discovery Report for Adoption

    Example:

    CresignSys Site Adoption
    ========================
    
    Domain:
    example.com
    
    Current PHP:
    8.2
    
    Current Nginx:
    Detected
    
    Current SSL:
    Valid until 2026-11-04
    
    Current Web Root:
    /storage/websites/example.com/public
    
    Current Owner:
    www-data:www-data
    
    CHP Management:
    READ-ONLY
    
    No changes will be made during discovery.

    Then:

    Use:
    hosting-adopt example.com

    when you’re ready.


    64. Drift Detection

    After import, CHP can periodically compare:

    DATABASE

    against:

    LIVE SERVER

    Example:

    Database:
    PHP 8.3
    
    Nginx:
    PHP 8.2

    Result:

    DRIFT

    65. hosting-reconcile

    This leads to a new command:

    sudo hosting-reconcile example.com

    It should initially be read-only.

    Output:

    CresignSys Reconciliation
    =========================
    
    PHP
      CHP:    8.3
      Actual: 8.2
      Result: DRIFT
    
    DNS
      CHP:    EXPECTED_SERVER
      Actual: EXPECTED_SERVER
      Result: MATCH
    
    SSL
      CHP:    ACTIVE
      Actual: ACTIVE
      Result: MATCH
    
    Overall:
    DRIFT DETECTED

    66. Reconciliation Is Different From Repair

    hosting-reconcile

    asks:

    What is different?

    While:

    hosting-repair

    asks:

    How do we fix it?

    This separation prevents accidental modifications.


    67. Current Migration Architecture

                      EXISTING SITE
                           │
                           ▼
                     DISCOVERY
                           │
                           ▼
                     DRY RUN
                           │
                           ▼
                     IMPORT
                           │
                           ▼
                     VERIFY
                           │
                           ▼
                    READ-ONLY
                           │
                 explicit adoption
                           │
                           ▼
                      MANAGED

    68. Recommended Migration Order

    For your existing server, don’t import all websites at once.

    Use:

    1. One test website
    2. Verify database
    3. Verify status
    4. Verify DNS
    5. Verify SSL
    6. Verify WordPress
    7. Verify backup
    8. Import second site
    9. Repeat
    10. Migrate remaining sites

    69. First Test Site

    Use a non-critical site first.

    For example:

    sudo hosting-db-import learn.cresignsys.com --dry-run

    Then:

    sudo hosting-db-import learn.cresignsys.com

    Then:

    sudo hosting-info learn.cresignsys.com

    Then:

    sudo hosting-site-status learn.cresignsys.com

    70. Verify Database

    Run:

    sqlite3 /var/lib/cresignsys/chp.db

    Then:

    SELECT id, primary_domain, management_state
    FROM sites;

    Expected:

    1|learn.cresignsys.com|VERIFIED

    71. Verify Domains

    SELECT site_id, domain, domain_type, dns_state, ssl_state
    FROM domains;

    Expected:

    1|learn.cresignsys.com|PRIMARY|CORRECT|ACTIVE

    72. Verify Services

    SELECT site_id, service_name, status, version
    FROM services;

    Expected:

    1|NGINX|OK|
    1|PHP_FPM|OK|8.3
    1|MYSQL|OK|8.0
    1|WORDPRESS|OK|

    73. Verify Operations

    SELECT id, site_id, operation, status
    FROM operations
    ORDER BY id DESC;

    Expected:

    1|1|IMPORT|SUCCESS

    74. The First Migration Goal

    After successful import:

    /storage/websites/learn.cresignsys.com/public

    must be unchanged.

    Nginx:

    UNCHANGED

    PHP:

    UNCHANGED

    MySQL:

    UNCHANGED

    WordPress:

    UNCHANGED

    Only:

    /var/lib/cresignsys/chp.db

    and CHP logs should change.


    75. This Is the Golden Migration Rule

    READ EXISTING INFRASTRUCTURE
            ↓
    UNDERSTAND IT
            ↓
    RECORD IT
            ↓
    VERIFY IT
            ↓
    ONLY THEN MANAGE IT

    Never reverse this order.


    76. Updated CHP Architecture

                             CHP
                              │
                  ┌───────────┴───────────┐
                  ▼                       ▼
            CONTROL PLANE             DATA PLANE
                  │                       │
                  ▼                       ▼
           CHP SQLite DB            Existing Website
                  │                       │
           ┌──────┼──────┐          ┌─────┼─────┐
           ▼      ▼      ▼          ▼     ▼     ▼
         Sites Domains Services    Nginx PHP  MySQL
           │      │      │          │     │     │
           └──────┼──────┘          └─────┼─────┘
                  │                       │
                  └──────────┬────────────┘
                             ▼
                        Reconciliation
                             │
                             ▼
                         Site Status

    77. Lesson 079 — Core Principle

    The existing server should not be rebuilt just to introduce CHP.

    Instead:

    EXISTING WEBSITES
           ↓
    DISCOVER
           ↓
    IMPORT
           ↓
    VERIFY
           ↓
    OPTIONALLY ADOPT
           ↓
    CHP MANAGEMENT

    This allows CresignSys Hosting Platform to grow around the websites you already have.


    Next Lesson — 080

    Build hosting-db-import Properly

    The next lesson will turn the design above into the actual migration implementation.

    We will build the discovery engine in stages:

    hosting-db-import
           │
           ├── 1. Validate domain
           ├── 2. Locate web root
           ├── 3. Detect owner
           ├── 4. Detect WordPress
           ├── 5. Detect database
           ├── 6. Detect PHP-FPM
           ├── 7. Detect Nginx
           ├── 8. Detect DNS
           ├── 9. Detect SSL
           ├── 10. Detect backups
           ├── 11. Generate discovery report
           ├── 12. Dry-run verification
           └── 13. Transactional database import

    The important implementation goal will be safe discovery first, database writing second.

  • CresignSys Learn — Lesson 078

    Build the CHP Site Database

    The filesystem model from the previous lessons works for a small number of websites:

    /var/lib/cresignsys/sites/example.com/site.conf

    But as CresignSys grows, we need structured information such as:

    Which sites exist?
    Which domains belong to each site?
    Which PHP version does each site use?
    Which backups exist?
    When was the last operation?
    What is the current state?
    Which SSL certificate belongs to which domains?

    A database is much better for this.


    1. Important Architecture Decision

    Do not use the customer WordPress databases for CHP metadata.

    For example:

    wordpress_example
    wordpress_shop
    wordpress_blog

    are application databases.

    CHP needs its own control-plane database:

    cresignsys

    So:

                        MySQL
                          │
            ┌─────────────┴─────────────┐
            ▼                           ▼
     CUSTOMER DATABASES            CHP DATABASE
            │                           │
            ▼                           ▼
     WordPress data              Hosting metadata

    2. What CHP Database Stores

    The CHP database should store:

    sites
    domains
    services
    backups
    SSL certificates
    operations

    It should not store:

    WordPress posts
    WordPress users
    WordPress media
    customer application tables

    Those remain in the site’s own database.


    3. Database Architecture

    The basic relationship:

                             sites
                               │
              ┌────────────────┼────────────────┐
              ▼                ▼                ▼
           domains          services          backups
              │
              ▼
        ssl_certificates
    
                  operations
                       │
                       ▼
                    sites

    4. Database Choice

    For the first CHP version, there are two reasonable choices:

    SQLite

    /var/lib/cresignsys/chp.db

    Advantages:

    • Very simple
    • No additional database server
    • Excellent for local control-plane metadata
    • Easy backup
    • Easy deployment

    MySQL

    cresignsys database

    Advantages:

    • Familiar in your current server environment
    • Better for future multi-node/control-panel architecture
    • Easier integration if the eventual platform becomes distributed

    For your current single-server CHP, I recommend starting with SQLite for CHP metadata, while keeping customer WordPress databases in MySQL.

    The architecture can later move to MySQL/PostgreSQL without changing the conceptual data model.


    5. Install SQLite

    Check:

    sqlite3 --version

    If unavailable:

    sudo apt update
    sudo apt install sqlite3

    Check:

    sqlite3 --version

    6. Create CHP Database Directory

    sudo mkdir -p /var/lib/cresignsys

    Create:

    sudo touch /var/lib/cresignsys/chp.db

    Set permissions:

    sudo chown root:root /var/lib/cresignsys/chp.db
    sudo chmod 640 /var/lib/cresignsys/chp.db

    7. Database Configuration

    Update:

    sudo nano /etc/cresignsys/hosting.conf

    Add:

    CHP_DB="/var/lib/cresignsys/chp.db"

    So the configuration becomes:

    CHP_ROOT="/etc/cresignsys"
    
    SITE_STATE_ROOT="/var/lib/cresignsys/sites"
    
    CHP_DB="/var/lib/cresignsys/chp.db"
    
    LOG_ROOT="/var/log/cresignsys"
    
    LOCK_ROOT="/var/lock/cresignsys"
    
    WEB_ROOT="/storage/websites"
    
    BACKUP_ROOT="/backup/cresignsys"
    
    SERVER_IPV4="YOUR_SERVER_IPV4"
    
    SERVER_IPV6=""
    
    DEFAULT_PHP_VERSION="8.3"

    8. Create Database Library

    Create:

    sudo nano /etc/cresignsys/lib/database.sh

    The library will eventually contain:

    db_init
    db_query
    db_exec
    db_transaction
    db_site_exists
    db_get_site
    db_add_site
    db_update_site

    9. Basic Database Function

    Start with:

    db_exec() {
        sqlite3 "$CHP_DB" "$1"
    }

    Then:

    db_exec "SELECT name FROM sqlite_master;"

    will query the database.


    10. Don’t Build SQL by Concatenating User Input

    Avoid:

    sqlite3 "$CHP_DB" "SELECT * FROM sites WHERE domain='$DOMAIN'"

    if:

    DOMAIN

    comes directly from a user.

    Instead, use SQLite’s parameter binding through a safer interface or strictly validated values.

    For the first internal scripts, domain validation provides an additional safety layer, but the database layer should still be designed for parameterized queries.


    11. Create the sites Table

    The first table:

    CREATE TABLE sites (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        primary_domain TEXT NOT NULL UNIQUE,
        web_root TEXT NOT NULL,
        php_version TEXT NOT NULL,
        site_user TEXT NOT NULL,
        service_state TEXT NOT NULL DEFAULT 'PROVISIONING',
        health_state TEXT NOT NULL DEFAULT 'UNKNOWN',
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL
    );

    12. Why id?

    Don’t use:

    example.com

    as the internal primary key.

    Use:

    id = 1

    Then:

    1 → example.com

    This makes relationships much cleaner.


    13. Example

    sites
    
    id | primary_domain | web_root | php_version | state
    -----------------------------------------------------
    1  | example.com   | ...      | 8.3         | ACTIVE
    2  | shop.com      | ...      | 8.3         | ACTIVE

    14. Why Keep primary_domain?

    Even though domains have their own table, keeping:

    sites.primary_domain

    is useful for fast access and clear site identity.

    However, the database must ensure it doesn’t contradict the domain table.

    Later we can refine this relationship.


    15. Create the domains Table

    CREATE TABLE domains (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER NOT NULL,
        domain TEXT NOT NULL UNIQUE,
        domain_type TEXT NOT NULL,
        redirect_target TEXT,
        dns_state TEXT NOT NULL DEFAULT 'PENDING',
        ssl_state TEXT NOT NULL DEFAULT 'PENDING',
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
    
        FOREIGN KEY (site_id)
            REFERENCES sites(id)
            ON DELETE CASCADE
    );

    16. Domain Types

    Use:

    PRIMARY
    ALIAS
    REDIRECT

    For example:

    example.com       PRIMARY
    www.example.com   ALIAS
    old-example.com   REDIRECT

    17. Domain Relationship

    The database now represents:

    SITE 1
     │
     ├── example.com
     ├── www.example.com
     └── old-example.com

    instead of storing:

    DOMAIN_LIST="example.com www.example.com old-example.com"

    inside a text field.

    This is much better.


    18. services Table

    Create:

    CREATE TABLE services (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER NOT NULL,
        service_name TEXT NOT NULL,
        status TEXT NOT NULL,
        version TEXT,
        socket_path TEXT,
        updated_at TEXT NOT NULL,
    
        FOREIGN KEY (site_id)
            REFERENCES sites(id)
            ON DELETE CASCADE
    );

    19. What Services Can Represent?

    Examples:

    NGINX
    PHP_FPM
    MYSQL
    WORDPRESS

    For example:

    site_id | service_name | status
    --------------------------------
    1       | NGINX        | OK
    1       | PHP_FPM      | OK
    1       | MYSQL        | OK
    1       | WORDPRESS    | OK

    20. Don’t Confuse Service State With Site State

    For example:

    site:
    ACTIVE

    while:

    PHP_FPM:
    FAIL

    is possible.

    Therefore:

    site state

    and:

    service health

    must remain separate.


    21. backups Table

    Create:

    CREATE TABLE backups (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER NOT NULL,
        backup_path TEXT NOT NULL,
        backup_type TEXT NOT NULL,
        size_bytes INTEGER,
        status TEXT NOT NULL,
        checksum TEXT,
        created_at TEXT NOT NULL,
        verified_at TEXT,
    
        FOREIGN KEY (site_id)
            REFERENCES sites(id)
            ON DELETE CASCADE
    );

    22. Backup Types

    For example:

    DAILY
    WEEKLY
    MONTHLY
    MANUAL
    PRE_RESTORE
    PRE_UPDATE

    This gives the system much more information than simply having files in a directory.


    23. Backup Status

    Use:

    CREATED
    VERIFIED
    FAILED
    DELETED

    Example:

    backup #102
    status = VERIFIED

    24. ssl_certificates Table

    Create:

    CREATE TABLE ssl_certificates (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER NOT NULL,
        certificate_path TEXT,
        private_key_path TEXT,
        issuer TEXT,
        expires_at TEXT,
        status TEXT NOT NULL,
        last_renewed_at TEXT,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
    
        FOREIGN KEY (site_id)
            REFERENCES sites(id)
            ON DELETE CASCADE
    );

    25. Certificate-Domain Relationship

    One certificate can cover:

    example.com
    www.example.com

    So eventually a many-to-many relationship may be useful.

    Create:

    CREATE TABLE certificate_domains (
        certificate_id INTEGER NOT NULL,
        domain_id INTEGER NOT NULL,
    
        PRIMARY KEY (certificate_id, domain_id),
    
        FOREIGN KEY (certificate_id)
            REFERENCES ssl_certificates(id)
            ON DELETE CASCADE,
    
        FOREIGN KEY (domain_id)
            REFERENCES domains(id)
            ON DELETE CASCADE
    );

    26. Why Separate This?

    Suppose:

    Certificate #1

    covers:

    example.com
    www.example.com

    Then:

    certificate_domains
    
    certificate_id | domain_id
    --------------------------
    1              | 1
    1              | 2

    This accurately represents the relationship.


    27. operations Table

    This will become extremely useful.

    CREATE TABLE operations (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        site_id INTEGER,
        operation TEXT NOT NULL,
        status TEXT NOT NULL,
        message TEXT,
        started_at TEXT NOT NULL,
        finished_at TEXT,
    
        FOREIGN KEY (site_id)
            REFERENCES sites(id)
            ON DELETE SET NULL
    );

    28. Example Operations

    CREATE
    DELETE
    SUSPEND
    UNSUSPEND
    BACKUP
    RESTORE
    REPAIR
    DOMAIN_ADD
    DOMAIN_REMOVE
    SSL_ISSUE
    SSL_RENEW

    29. Why Operations Matter

    Suppose:

    hosting-restore example.com 102

    fails.

    The database can record:

    operation:
    RESTORE
    
    status:
    FAILED
    
    message:
    Database restore failed
    
    started:
    19:20
    
    finished:
    19:24

    Now the control panel has a history.


    30. Complete Database Model

                             SITES
                               │
              ┌────────────────┼─────────────────┐
              │                │                 │
              ▼                ▼                 ▼
           DOMAINS          SERVICES          BACKUPS
              │
              ▼
       CERTIFICATE_DOMAINS
              │
              ▼
       SSL_CERTIFICATES
    
                  OPERATIONS
                       │
                       ▼
                     SITES

    31. Create the Schema File

    Create:

    sudo mkdir -p /etc/cresignsys/schema

    Then:

    sudo nano /etc/cresignsys/schema/001_initial.sql

    Put all initial CREATE TABLE statements there.

    This is better than putting SQL directly inside the Bash scripts.


    32. Schema Versioning

    Later:

    schema/
    ├── 001_initial.sql
    ├── 002_add_site_limits.sql
    ├── 003_add_users.sql
    ├── 004_add_dns_records.sql

    This allows controlled database upgrades.


    33. Migration Table

    Create:

    CREATE TABLE schema_migrations (
        version INTEGER PRIMARY KEY,
        filename TEXT NOT NULL,
        applied_at TEXT NOT NULL
    );

    Then CHP knows:

    Current schema:
    4

    and can determine whether migrations are needed.


    34. Why Migrations Matter

    Imagine version 1 has:

    sites

    Later you add:

    storage_limit

    You don’t want to manually recreate the entire database.

    Instead:

    001
     ↓
    002
     ↓
    003
     ↓
    004

    updates the existing installation.


    35. Create hosting-db-init

    Create:

    sudo nano /usr/local/bin/hosting-db-init

    The command should:

    1. Check SQLite
    2. Create database
    3. Apply schema
    4. Record migration
    5. Verify tables

    36. Expected Output

    CresignSys Database Initialization
    ==================================
    
    Database:
     /var/lib/cresignsys/chp.db
    
    [OK] SQLite available
    [OK] Database created
    [OK] Schema 001 applied
    [OK] Sites table
    [OK] Domains table
    [OK] Services table
    [OK] Backups table
    [OK] SSL table
    [OK] Operations table
    
    Database:
    READY

    37. Verify Manually

    Run:

    sqlite3 /var/lib/cresignsys/chp.db

    Then:

    .tables

    Expected:

    backups
    certificate_domains
    domains
    operations
    schema_migrations
    services
    sites
    ssl_certificates

    Exit:

    .quit

    38. Add Indexes

    As the database grows, indexes become important.

    For domains:

    CREATE INDEX idx_domains_site_id
    ON domains(site_id);

    For services:

    CREATE INDEX idx_services_site_id
    ON services(site_id);

    For backups:

    CREATE INDEX idx_backups_site_id
    ON backups(site_id);

    For operations:

    CREATE INDEX idx_operations_site_id
    ON operations(site_id);

    39. Domain Lookup

    Because:

    domain TEXT UNIQUE

    we can quickly find:

    example.com

    and determine:

    site_id = 1

    This becomes the foundation of nearly every CHP command.


    40. Site Lookup Flow

    When the user runs:

    hosting-info example.com

    the flow becomes:

    example.com
         │
         ▼
    domains table
         │
         ▼
    site_id = 1
         │
         ▼
    sites table
         │
         ├── web root
         ├── PHP version
         ├── state
         └── health

    41. Why Domain Should Be the User-Facing Identifier

    Users naturally know:

    example.com

    not:

    site_id=17

    Therefore commands should continue to accept:

    hosting-info example.com

    while internally using:

    site_id

    42. Site ID Should Be Internal

    Use:

    site_id=17

    for:

    foreign keys
    database relationships
    internal operations

    but not as the primary customer-facing interface.


    43. Add Site

    A future hosting-create workflow:

    hosting-create example.com
           │
           ▼
    Validate domain
           │
           ▼
    Create filesystem
           │
           ▼
    Create database record
           │
           ▼
    Create domain record
           │
           ▼
    Create service records
           │
           ▼
    Generate Nginx

    44. Database Transaction

    This is where transactions become important.

    Suppose:

    filesystem created
    database record created
    domain record failed

    Now the system is inconsistent.

    A transaction helps with the database portion:

    BEGIN
       create site
       create domain
       create services
    COMMIT

    If a database operation fails:

    ROLLBACK

    45. But Transactions Don’t Roll Back Filesystem Changes

    This is important.

    A database transaction cannot undo:

    mkdir /storage/websites/example.com

    Therefore the full operation needs a compensation strategy:

    DATABASE TRANSACTION
    +
    FILESYSTEM ROLLBACK

    46. Provisioning Pattern

    Use:

    PREPARE
       ↓
    FILESYSTEM CREATE
       ↓
    DATABASE TRANSACTION
       ↓
    CONFIG GENERATION
       ↓
    VALIDATE
       ↓
    COMMIT OPERATION

    If something fails:

    ROLLBACK DATABASE
    ROLLBACK FILESYSTEM
    REMOVE TEMP CONFIG

    where safe.


    47. Don’t Delete Customer Data Automatically After Every Failure

    Rollback must be carefully classified.

    For a brand-new site:

    creation failed

    removing the newly created empty directory may be safe.

    For an existing site:

    repair failed

    automatically deleting the website would be dangerous.

    Therefore:

    CREATE

    and:

    REPAIR

    need different rollback policies.


    48. Database and Existing Sites

    You already have websites created under:

    /storage/websites/

    Don’t immediately migrate everything destructively.

    Use:

    DISCOVER
       ↓
    IMPORT
       ↓
    VERIFY
       ↓
    ACTIVATE DATABASE MANAGEMENT

    49. Build hosting-db-import

    Eventually:

    sudo hosting-db-import example.com

    will inspect the existing site:

    /storage/websites/example.com

    and create corresponding database records.


    50. Import Example

    Existing:

    /storage/websites/example.com/public

    CHP discovers:

    domain:
    example.com
    
    web root:
    /storage/websites/example.com/public
    
    PHP:
    8.3
    
    Nginx:
    exists
    
    WordPress:
    installed

    Then creates:

    site_id = 1

    51. Import Must Be Idempotent

    If you run:

    hosting-db-import example.com

    twice, it should not create:

    site 1
    site 2

    for the same site.

    It should say:

    Site already imported.

    or:

    Site already exists.

    52. Database Becomes Source of Truth

    After successful migration:

    CHP DATABASE

    should become authoritative for:

    site identity
    domain relationships
    service metadata
    backup inventory
    SSL metadata
    operation history

    Filesystem remains authoritative for:

    actual website files

    53. Two Different Kinds of Truth

    This distinction is critical.

    Control-plane truth

    Database

    knows:

    What should exist?

    Data-plane truth

    Filesystem/Nginx/PHP/MySQL

    knows:

    What actually exists and is running?

    54. Reconciliation

    This leads to an important future feature:

    hosting-reconcile example.com

    It compares:

    DATABASE
          VS
    ACTUAL SERVER

    55. Example Reconciliation

    Database says:

    PHP_VERSION=8.3

    Actual Nginx configuration says:

    PHP 8.2

    Then:

    DRIFT DETECTED

    56. Another Example

    Database says:

    www.example.com

    belongs to:

    site_id=1

    but Nginx configuration doesn’t contain it.

    Result:

    CONFIGURATION DRIFT

    57. Another Example

    Database says:

    SSL=ACTIVE

    but certificate is expired.

    Result:

    STATE DRIFT

    The platform can then recommend:

    hosting-ssl example.com

    58. Database Status vs Actual Health

    Never assume:

    database.status = truth

    for runtime health.

    For example:

    DB metadata:
    PHP_FPM = OK

    but the process may have crashed.

    So:

    DATABASE

    stores the expected/configured state.

    Runtime checks determine:

    ACTUAL HEALTH

    59. This Creates a Powerful Model

                        CHP DATABASE
                             │
                      DESIRED STATE
                             │
                             ▼
                       RECONCILIATION
                             │
                             ▼
                       ACTUAL SERVER
                             │
                             ▼
                        HEALTH CHECK

    This is similar to how larger infrastructure-management systems work.


    60. hosting-info

    After the database migration, the command can become:

    hosting-info example.com

    Output:

    CresignSys Site Information
    ===========================
    
    Site ID:
    1
    
    Primary Domain:
    example.com
    
    Web Root:
    /storage/websites/example.com/public
    
    PHP:
    8.3
    
    Service State:
    ACTIVE
    
    Health:
    HEALTHY
    
    Domains:
      example.com
      www.example.com
    
    Backups:
      12
    
    SSL:
      ACTIVE
    
    Created:
    2026-08-13 19:00:00

    61. hosting-list

    Instead of scanning directories:

    ls /storage/websites

    it can query:

    SELECT ...
    FROM sites
    ORDER BY primary_domain;

    Then display:

    DOMAIN                  STATE       HEALTH
    ------------------------------------------------
    example.com             ACTIVE      HEALTHY
    shop.example.com        ACTIVE      HEALTHY
    medical.example.com     SUSPENDED   HEALTHY

    62. hosting-domain-list

    Query:

    SELECT domain, domain_type, dns_state, ssl_state
    FROM domains
    WHERE site_id = ?;

    Output:

    DOMAIN                 TYPE       DNS       SSL
    ------------------------------------------------
    example.com             PRIMARY    CORRECT   ACTIVE
    www.example.com         ALIAS      CORRECT   ACTIVE
    old.example.com         REDIRECT   CORRECT   ACTIVE

    63. hosting-backup-list

    Query:

    SELECT id, backup_type, size_bytes, status, created_at
    FROM backups
    WHERE site_id = ?
    ORDER BY created_at DESC;

    Output:

    ID    TYPE       STATUS      DATE
    -----------------------------------------
    102   DAILY      VERIFIED    Aug 13
    101   DAILY      VERIFIED    Aug 12
    100   WEEKLY     VERIFIED    Aug 11

    64. hosting-site-status

    This command now has two information sources:

    DATABASE
       +
    LIVE CHECKS

    For example:

    Configured PHP:
    8.3
    
    Actual PHP:
    8.3
    
    Result:
    OK

    65. Database as Control Plane

    The complete model becomes:

                     CHP CONTROL PLANE
                            │
                            ▼
                     SQLite Database
                            │
           ┌────────────────┼────────────────┐
           ▼                ▼                ▼
         Sites           Domains          Backups
           │                │                │
           ▼                ▼                ▼
       Services            SSL           Operations
                            │
                            ▼
                     DESIRED STATE
                            │
                            ▼
                      SERVER ENGINE

    66. Customer Data Plane

                        DATA PLANE
                            │
             ┌──────────────┼──────────────┐
             ▼              ▼              ▼
         WordPress        MySQL          Uploads
             │              │              │
             └──────────────┼──────────────┘
                            ▼
                      Website Traffic

    67. Why This Separation Is Powerful

    If the CHP database becomes corrupted:

    customer website files

    can still exist.

    If a WordPress database breaks:

    CHP database

    can still know:

    site exists
    domain exists
    backup exists
    PHP version
    service state

    This separation improves recovery.


    68. CHP Database Backup

    Because the CHP database is now critical, back it up.

    For SQLite:

    sqlite3 /var/lib/cresignsys/chp.db \
        ".backup '/backup/cresignsys/chp.db'"

    Do this regularly.


    69. Don’t Store Backups Only Inside the Same Disk

    If:

    /storage

    and:

    /backup

    are on the same physical disk and that disk fails, both can disappear.

    Eventually use:

    LOCAL BACKUP
    +
    REMOTE BACKUP

    for important hosting data.


    70. CHP Database Restore

    You should eventually have:

    hosting-db-backup
    hosting-db-verify
    hosting-db-restore

    But these are infrastructure-level commands and should be protected carefully.


    71. Database Locking

    SQLite handles concurrent access well for many control-plane workloads, but writes still need coordination.

    Your existing CHP operation lock remains useful:

    SITE LOCK

    For global database migrations:

    GLOBAL CHP LOCK

    72. Two Lock Levels

    Use:

    /var/lock/cresignsys/site-example.com.lock

    for site operations.

    And:

    /var/lock/cresignsys/chp-global.lock

    for:

    schema migration
    database restore
    global configuration changes

    73. Never Run Schema Migration During Normal Operations

    Don’t let:

    hosting-site-status

    automatically modify the schema.

    Database schema changes should be explicit:

    sudo hosting-db-migrate

    74. Database Migration Command

    Create:

    sudo nano /usr/local/bin/hosting-db-migrate

    Its workflow:

    Check root
       ↓
    Acquire global lock
       ↓
    Read current schema
       ↓
    Find pending migrations
       ↓
    Apply transaction
       ↓
    Record migration
       ↓
    Verify
       ↓
    Release lock

    75. Migration Output

    CresignSys Database Migration
    =============================
    
    Current schema:
    1
    
    Available schema:
    4
    
    Applying:
    002_add_site_limits.sql
    [OK]
    
    Applying:
    003_add_dns_records.sql
    [OK]
    
    Applying:
    004_add_users.sql
    [OK]
    
    Database:
    UP TO DATE

    76. Future Site Limits

    The database can later support:

    storage_limit
    bandwidth_limit
    database_limit
    domain_limit
    backup_limit

    Example:

    sites
    -----------------------------------------
    storage_limit_mb = 10240
    domain_limit = 10
    backup_retention_days = 30

    This becomes the foundation for hosting plans.


    77. Hosting Plans

    Eventually:

    plans
    -----
    Starter
    Business
    Professional
    Multi-Domain

    and:

    site_plans
    ----------
    site_id
    plan_id

    Then CHP can enforce:

    maximum storage
    maximum domains
    maximum databases
    maximum backups

    This connects directly to your future reseller/hosting business architecture.


    78. Future Customer Accounts

    Later:

    customers
        │
        ▼
    sites

    Example:

    Customer
       │
       ├── example.com
       ├── shop.example.com
       └── blog.example.com

    This is the foundation for a real hosting control panel.


    79. Future Users

    Eventually:

    customers
    users
    sites
    domains

    can support:

    Administrator
    Reseller
    Customer
    Developer
    Support

    with different permissions.

    But don’t implement this yet.


    80. Current Database Scope

    For now keep it to:

    sites
    domains
    services
    backups
    ssl_certificates
    certificate_domains
    operations
    schema_migrations

    That is enough for the current platform.


    81. Recommended Schema

    The initial CHP schema should therefore look like:

    sites
    ├── id
    ├── primary_domain
    ├── web_root
    ├── php_version
    ├── site_user
    ├── service_state
    ├── health_state
    ├── created_at
    └── updated_at
    
    domains
    ├── id
    ├── site_id
    ├── domain
    ├── domain_type
    ├── redirect_target
    ├── dns_state
    ├── ssl_state
    ├── created_at
    └── updated_at
    
    services
    ├── id
    ├── site_id
    ├── service_name
    ├── status
    ├── version
    ├── socket_path
    └── updated_at
    
    backups
    ├── id
    ├── site_id
    ├── backup_path
    ├── backup_type
    ├── size_bytes
    ├── status
    ├── checksum
    ├── created_at
    └── verified_at
    
    ssl_certificates
    ├── id
    ├── site_id
    ├── certificate_path
    ├── private_key_path
    ├── issuer
    ├── expires_at
    ├── status
    └── timestamps
    
    operations
    ├── id
    ├── site_id
    ├── operation
    ├── status
    ├── message
    ├── started_at
    └── finished_at

    82. Migration Strategy

    Do not immediately delete:

    /var/lib/cresignsys/sites/

    The migration should be:

    OLD FILESYSTEM STATE
            │
            ▼
    DISCOVER
            │
            ▼
    IMPORT INTO DATABASE
            │
            ▼
    VERIFY
            │
            ▼
    DATABASE BECOMES AUTHORITATIVE
            │
            ▼
    OLD STATE KEPT AS FALLBACK

    Only remove the old state after successful verification.


    83. First Database Command Set

    We now add:

    hosting-db-init
    hosting-db-migrate
    hosting-db-backup
    hosting-db-verify
    hosting-db-import

    Eventually:

    hosting-db-restore

    84. Updated CHP Command Architecture

    SYSTEM
    │
    ├── Database
    │   ├── hosting-db-init
    │   ├── hosting-db-migrate
    │   ├── hosting-db-backup
    │   ├── hosting-db-verify
    │   └── hosting-db-import
    │
    ├── Sites
    │   ├── hosting-create
    │   ├── hosting-list
    │   ├── hosting-info
    │   ├── hosting-site-status
    │   └── hosting-health
    │
    ├── Domains
    │   ├── hosting-domain-add
    │   ├── hosting-domain-list
    │   └── hosting-domain-remove
    │
    ├── DNS
    │   └── hosting-dns-check
    │
    ├── SSL
    │   └── hosting-ssl
    │
    ├── Backup
    │   ├── hosting-backup
    │   ├── hosting-backup-list
    │   ├── hosting-backup-verify
    │   ├── hosting-backup-prune
    │   └── hosting-restore
    │
    └── Service
        ├── hosting-suspend
        ├── hosting-unsuspend
        └── hosting-repair

    85. Lesson 078 — Core Principle

    We now have a clear distinction:

                        CRESIGNSYS
                             │
                 ┌───────────┴───────────┐
                 ▼                       ▼
           CONTROL PLANE             DATA PLANE
                 │                       │
                 ▼                       ▼
           CHP Database             Website Files
                 │                   WordPress
                 │                   MySQL
                 │                   Uploads
                 ▼
           Desired State
                 │
                 ▼
           CHP Engine
                 │
                 ▼
           Nginx / PHP / SSL

    The database is the control plane.

    The website files and application databases remain the data plane.

    That separation is what allows CresignSys to eventually become a proper hosting-management platform rather than a collection of shell scripts.


    Next Lesson — 079

    Migrate Existing Websites Into the CHP Database

    The next step should be practical because your server already contains websites.

    We will build:

    sudo hosting-db-import example.com

    which will inspect an existing website and discover:

    Domain
    Web root
    Linux owner
    PHP version
    PHP-FPM socket
    Nginx configuration
    WordPress installation
    WordPress database
    Database name
    SSL status
    Backup configuration

    Then it will create the corresponding:

    sites
    domains
    services
    ssl_certificates

    records without modifying the existing website.

    The migration principle will be:

    EXISTING WEBSITE
           ↓
    READ ONLY DISCOVERY
           ↓
    CHP DATABASE
           ↓
    VERIFY
           ↓
    CHP MANAGEMENT

    This is the safest way to bring your existing /storage/websites/... installations under CHP management.

  • CresignSys Learn — Lesson 077

    Build the Shared CHP Core Library

    We have reached an important point.

    The platform currently has many commands:

    hosting-create
    hosting-list
    hosting-info
    hosting-health
    hosting-site-status
    hosting-repair
    hosting-ssl
    hosting-backup
    hosting-restore
    hosting-suspend
    hosting-unsuspend
    hosting-domain-add
    hosting-domain-list
    hosting-domain-remove
    hosting-dns-check

    If each script contains its own:

    domain validation
    logging
    locking
    error handling
    Nginx handling
    PHP handling
    MySQL handling

    the system will eventually become difficult to maintain.

    So now we create the CresignSys Hosting Platform Core.


    1. New Architecture

    Instead of:

    hosting-create
       └── 500 lines
    
    hosting-health
       └── 500 lines
    
    hosting-repair
       └── 500 lines

    we want:

                        CHP CORE
                           │
           ┌───────────────┼────────────────┐
           ▼               ▼                ▼
        Commands        Libraries        Templates
           │               │                │
           ▼               ▼                ▼
     hosting-*       common functions    Nginx/PHP

    The commands become thin interfaces.


    2. Final Directory Structure

    Create:

    /etc/cresignsys/
    ├── hosting.conf
    ├── lib/
    │   ├── common.sh
    │   ├── domain.sh
    │   ├── dns.sh
    │   ├── nginx.sh
    │   ├── php.sh
    │   ├── mysql.sh
    │   ├── wordpress.sh
    │   ├── ssl.sh
    │   ├── backup.sh
    │   ├── logging.sh
    │   └── lock.sh
    │
    ├── templates/
    │   ├── nginx/
    │   ├── php/
    │   └── suspended/
    │
    └── sites/

    Site state:

    /var/lib/cresignsys/
    └── sites/
        └── example.com/
            ├── site.conf
            ├── domains.conf
            └── state

    Logs:

    /var/log/cresignsys/
    ├── operations.log
    ├── errors.log
    └── audit.log

    3. Create the Structure

    Run:

    sudo mkdir -p /etc/cresignsys/lib
    sudo mkdir -p /etc/cresignsys/templates/nginx
    sudo mkdir -p /etc/cresignsys/templates/php
    sudo mkdir -p /etc/cresignsys/templates/suspended
    
    sudo mkdir -p /var/lib/cresignsys/sites
    sudo mkdir -p /var/log/cresignsys
    sudo mkdir -p /var/lock/cresignsys

    4. Central Configuration

    Create:

    sudo nano /etc/cresignsys/hosting.conf

    Example:

    CHP_ROOT="/etc/cresignsys"
    
    SITE_STATE_ROOT="/var/lib/cresignsys/sites"
    
    LOG_ROOT="/var/log/cresignsys"
    
    LOCK_ROOT="/var/lock/cresignsys"
    
    WEB_ROOT="/storage/websites"
    
    BACKUP_ROOT="/backup/cresignsys"
    
    SERVER_IPV4="YOUR_SERVER_IPV4"
    
    SERVER_IPV6=""
    
    DEFAULT_PHP_VERSION="8.3"
    
    BACKUP_DAILY_RETENTION=7
    BACKUP_WEEKLY_RETENTION=4
    BACKUP_MONTHLY_RETENTION=3

    Replace:

    YOUR_SERVER_IPV4

    with the actual server IP.


    5. Configuration Permissions

    Because this file contains infrastructure settings:

    sudo chown root:root /etc/cresignsys/hosting.conf
    sudo chmod 640 /etc/cresignsys/hosting.conf

    Don’t make the configuration world-writable.


    6. common.sh

    Create:

    sudo nano /etc/cresignsys/lib/common.sh

    This becomes the foundation for every command.

    It should contain:

    require_root
    command_exists
    die
    log_info
    log_warn
    log_error
    timestamp

    7. Root Check

    Function:

    require_root() {
        if [[ "$EUID" -ne 0 ]]; then
            echo "ERROR: This command must be run as root."
            exit 1
        fi
    }

    Now every command can simply call:

    require_root

    8. Command Existence

    Add:

    command_exists() {
        command -v "$1" >/dev/null 2>&1
    }

    Usage:

    if ! command_exists nginx; then
        die "nginx command not found"
    fi

    9. die()

    die() {
        echo "ERROR: $*" >&2
        exit 1
    }

    Then:

    [[ -d "$WEB_ROOT" ]] || die "Web root does not exist"

    10. Timestamp

    timestamp() {
        date '+%Y-%m-%d %H:%M:%S'
    }

    This gives consistent timestamps throughout the platform.


    11. Logging Library

    Create:

    sudo nano /etc/cresignsys/lib/logging.sh

    Use:

    operations.log
    errors.log
    audit.log

    12. Operation Log

    Example:

    2026-08-13 19:20:00 INFO hosting-create example.com

    Function:

    log_info() {
        printf '%s INFO %s\n' "$(timestamp)" "$*" \
            >> "${LOG_ROOT}/operations.log"
    }

    13. Warning Log

    log_warn() {
        printf '%s WARN %s\n' "$(timestamp)" "$*" \
            >> "${LOG_ROOT}/operations.log"
    }

    14. Error Log

    log_error() {
        printf '%s ERROR %s\n' "$(timestamp)" "$*" \
            >> "${LOG_ROOT}/errors.log"
    }

    15. Audit Log

    Audit logs should record actions that change customer state.

    Example:

    2026-08-13 19:30:00
    operator=root
    action=SUSPEND
    domain=example.com

    Function:

    audit_log() {
        printf '%s AUDIT %s\n' "$(timestamp)" "$*" \
            >> "${LOG_ROOT}/audit.log"
    }

    16. Protect Logs

    Run:

    sudo chown -R root:root /var/log/cresignsys
    sudo chmod 750 /var/log/cresignsys

    Individual logs can be:

    sudo chmod 640 /var/log/cresignsys/*.log

    17. Domain Library

    Create:

    sudo nano /etc/cresignsys/lib/domain.sh

    This should contain all domain-related functions.


    18. Normalize Domain

    normalize_domain() {
        printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
    }

    Example:

    Example.COM

    becomes:

    example.com

    19. Domain Validation

    The function:

    validate_domain()

    should reject:

    https://example.com
    example.com/path
    example.com:443

    and accept:

    example.com
    www.example.com
    shop.example.com

    Keep this validation centralized.


    20. Why Central Validation Matters

    Previously:

    hosting-domain-add

    might validate one way while:

    hosting-dns-check

    validates another way.

    Now both use:

    validate_domain()

    Therefore behavior stays consistent.


    21. Site Existence

    Add:

    site_exists() {
        [[ -f "${SITE_STATE_ROOT}/$1/site.conf" ]]
    }

    Then:

    if ! site_exists "$DOMAIN"; then
        die "Site does not exist: $DOMAIN"
    fi

    22. DNS Library

    Create:

    sudo nano /etc/cresignsys/lib/dns.sh

    Functions:

    get_a_records
    get_aaaa_records
    get_cname
    get_nameservers
    get_ttl
    check_dns

    23. get_a_records

    Conceptually:

    get_a_records() {
        dig +short A "$1"
    }

    Now every command uses the same DNS mechanism.


    24. get_cname

    get_cname() {
        dig +short CNAME "$1"
    }

    25. get_nameservers

    get_nameservers() {
        dig +short NS "$1"
    }

    26. Nginx Library

    Create:

    sudo nano /etc/cresignsys/lib/nginx.sh

    Functions:

    nginx_is_running
    nginx_config_test
    reload_nginx
    site_nginx_config_exists

    27. Nginx Running Check

    nginx_is_running() {
        systemctl is-active --quiet nginx
    }

    Usage:

    if nginx_is_running; then
        echo "Nginx OK"
    fi

    28. Nginx Configuration Test

    nginx_config_test() {
        nginx -t >/dev/null 2>&1
    }

    This becomes reusable everywhere.


    29. Reload Nginx

    reload_nginx() {
        systemctl reload nginx
    }

    Then:

    nginx_config_test || die "Nginx configuration invalid"
    
    reload_nginx

    30. PHP Library

    Create:

    sudo nano /etc/cresignsys/lib/php.sh

    Functions:

    php_fpm_service
    php_fpm_running
    php_socket_exists

    31. PHP-FPM Service

    php_fpm_service() {
        printf 'php%s-fpm' "$1"
    }

    For:

    8.3

    returns:

    php8.3-fpm

    32. PHP-FPM Check

    php_fpm_running() {
        local version="$1"
        systemctl is-active --quiet "$(php_fpm_service "$version")"
    }

    33. MySQL Library

    Create:

    sudo nano /etc/cresignsys/lib/mysql.sh

    Functions:

    mysql_running
    database_exists

    34. MySQL Running

    mysql_running() {
        systemctl is-active --quiet mysql
    }

    35. Database Existence

    A first implementation can check:

    mysql -NBe "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA ..."

    But avoid constructing SQL with unvalidated user input.

    Use the internally stored database name after validating it.


    36. WordPress Library

    Create:

    sudo nano /etc/cresignsys/lib/wordpress.sh

    Functions:

    wordpress_installed
    wordpress_db_check
    wordpress_version

    37. WordPress Installed

    wordpress_installed() {
        local root="$1"
        wp --path="$root" core is-installed >/dev/null 2>&1
    }

    38. SSL Library

    Create:

    sudo nano /etc/cresignsys/lib/ssl.sh

    Functions:

    certificate_exists
    certificate_expiry
    certificate_matches_domain

    This prevents SSL logic being duplicated between:

    hosting-ssl
    hosting-health
    hosting-site-status

    39. Backup Library

    Create:

    sudo nano /etc/cresignsys/lib/backup.sh

    Functions:

    backup_exists
    latest_backup
    backup_status
    backup_age
    backup_is_valid

    40. Lock Library

    Create:

    sudo nano /etc/cresignsys/lib/lock.sh

    This is particularly important.


    41. Why Locking Is Needed

    Imagine:

    17:00 hosting-backup example.com
    17:01 hosting-restore example.com

    Both modify:

    /storage/websites/example.com

    Without locking:

    BACKUP
          │
          ├── reading files
          │
          └───────┐
                  │
    RESTORE      │
          │      │
          └──────┘

    This can create inconsistent results.


    42. Site Lock

    Lock path:

    /var/lock/cresignsys/example.com.lock

    The operation acquires:

    LOCK

    before modifying the site.


    43. Use flock

    Linux already provides a reliable locking mechanism.

    Conceptually:

    exec 200>"${LOCK_ROOT}/${DOMAIN}.lock"
    flock -n 200

    If the lock cannot be acquired:

    ERROR:
    Another operation is already running.

    44. Don’t Invent Locking With Temporary Files

    Avoid:

    if [ -f lock ]; then ...
    touch lock

    because two processes can race between:

    check

    and:

    create

    flock is designed for this problem.


    45. Common Library Loading

    Every command can start with:

    source /etc/cresignsys/hosting.conf
    source /etc/cresignsys/lib/common.sh
    source /etc/cresignsys/lib/logging.sh
    source /etc/cresignsys/lib/lock.sh

    Then add only the libraries it needs.

    For example:

    hosting-dns-check
     ↓
    common.sh
    dns.sh
    logging.sh

    46. hosting-site-status

    Now becomes much simpler:

    load config
    load libraries
    validate domain
    load site state
    
    check DNS
    check Nginx
    check HTTP
    check HTTPS
    check SSL
    check PHP
    check database
    check WordPress
    check files
    check backup
    
    calculate overall status
    display result

    The complexity moves into reusable functions.


    47. Error Handling

    Use:

    set -Eeuo pipefail

    but be careful.

    A health check is intentionally expected to encounter failures.

    For example:

    if php_fpm_running "$PHP_VERSION"; then
        PHP_STATUS="OK"
    else
        PHP_STATUS="FAIL"
    fi

    Do not let set -e terminate the entire diagnostic before collecting all results.


    48. Diagnostic Commands vs Action Commands

    This distinction is important.

    Action

    hosting-repair

    A failure may justify stopping immediately.

    Diagnostic

    hosting-site-status

    A failure should usually be recorded and the next check should continue.


    49. Example Diagnostic Flow

    DNS       FAIL
    Nginx     OK
    PHP       OK
    Database  FAIL
    WordPress FAIL
    Backup    OK

    The user receives the full picture.


    50. State Library

    Add another library:

    /etc/cresignsys/lib/state.sh

    Functions:

    get_service_state
    set_service_state
    get_health_state
    set_health_state

    51. State Transitions

    Don’t allow arbitrary changes.

    For example:

    ACTIVE → SUSPENDING

    is valid.

    But:

    DELETED → ACTIVE

    should not happen through normal operations.


    52. Centralize State Transitions

    Instead of:

    echo "ACTIVE" > state

    in every script, use:

    set_service_state "$DOMAIN" "ACTIVE"

    This gives you one place to enforce rules and logging.


    53. State File

    A simple initial state file:

    SERVICE_STATE=ACTIVE
    HEALTH_STATE=HEALTHY

    But don’t let arbitrary users modify it.

    Use:

    root:root

    ownership.


    54. Better Future Design

    Eventually move state to a database:

    sites
    domains
    services
    backups
    operations

    But a structured filesystem state model is acceptable for the first CHP implementation.


    55. Operation Library

    Create:

    /etc/cresignsys/lib/operation.sh

    It can provide:

    operation_start
    operation_success
    operation_failure

    Example:

    START
     ↓
    LOCK
     ↓
    ACTION
     ↓
    VERIFY
     ↓
    SUCCESS

    56. Standard Operation Pattern

    Every modifying command should follow:

    VALIDATE
       ↓
    LOCK
       ↓
    PRECHECK
       ↓
    CHANGE
       ↓
    VERIFY
       ↓
    STATE UPDATE
       ↓
    AUDIT

    This becomes the standard CHP operational pattern.


    57. Example: hosting-suspend

    VALIDATE DOMAIN
           ↓
    LOCK SITE
           ↓
    CHECK ACTIVE
           ↓
    SET SUSPENDING
           ↓
    GENERATE NGINX
           ↓
    nginx -t
           ↓
    RELOAD
           ↓
    HTTP CHECK
           ↓
    SET SUSPENDED
           ↓
    AUDIT

    58. Example: hosting-restore

    VALIDATE BACKUP
           ↓
    LOCK SITE
           ↓
    PRE-RESTORE BACKUP
           ↓
    RESTORE
           ↓
    VERIFY
           ↓
    HEALTH CHECK
           ↓
    STATE UPDATE
           ↓
    AUDIT

    59. Example: hosting-domain-add

    VALIDATE DOMAIN
           ↓
    CHECK OWNERSHIP/POLICY
           ↓
    LOCK SITE
           ↓
    REGISTER DOMAIN
           ↓
    GENERATE NGINX
           ↓
    nginx -t
           ↓
    RELOAD
           ↓
    UPDATE SSL STATE
           ↓
    AUDIT

    60. Templates

    Now create:

    /etc/cresignsys/templates/nginx/

    Eventually:

    active.conf.tpl
    suspended.conf.tpl
    redirect.conf.tpl

    61. Active Template

    Conceptually:

    server {
        listen 80;
        server_name {{DOMAINS}};
    
        root {{WEB_ROOT}};
        index index.php index.html;
    
        location / {
            try_files $uri $uri/ /index.php?$args;
        }
    }

    The actual template should be generated by your CHP template engine or carefully rendered using safe substitutions.


    62. Suspended Template

    Conceptually:

    server {
        listen 80;
        server_name {{DOMAINS}};
    
        return 503;
    }

    The HTTPS server block should similarly preserve the site’s TLS configuration while serving the suspension response.


    63. Redirect Template

    server {
        listen 80;
        server_name {{DOMAIN}};
    
        return 301 https://{{TARGET}}$request_uri;
    }

    Again, HTTPS handling must be generated consistently with the site’s certificate configuration.


    64. Template Principle

    Never make the generated configuration depend on:

    what happened last time

    Instead:

    CURRENT METADATA
          ↓
    CURRENT DESIRED STATE
          ↓
    GENERATE COMPLETE CONFIG

    This makes repair much easier.


    65. Configuration Generation

    The ideal architecture becomes:

    site.conf
    domains.conf
    service state
    PHP version
    SSL state
           │
           ▼
       CONFIG ENGINE
           │
           ▼
    Nginx configuration

    66. Validate Before Activation

    Never directly overwrite the active configuration.

    Use:

    generate
       ↓
    temporary file
       ↓
    nginx -t
       ↓
    activate

    For example:

    example.com.conf.new

    then after successful validation:

    example.com.conf

    67. Atomic Configuration Replacement

    The activation step should ideally be atomic:

    OLD CONFIG
         │
         ▼
    NEW CONFIG VALIDATED
         │
         ▼
    atomic replacement

    This avoids leaving partially written configuration files.


    68. Backup the Configuration

    Before a major configuration change:

    example.com.conf

    can be copied to:

    example.com.conf.previous

    This gives another recovery mechanism.


    69. Don’t Accumulate .previous Files Forever

    Configuration backups should have a retention policy too.

    For example:

    current
    previous

    may be sufficient for fast rollback.

    Historical configuration versions should instead be stored in the audit/versioning system if needed.


    70. File Ownership

    Infrastructure configuration should normally be:

    root:root

    and not writable by:

    www-data

    This prevents a compromised website from modifying its own Nginx configuration.


    71. Security Boundary

    The architecture should therefore look like:

                       ROOT
                        │
                ┌───────┴────────┐
                ▼                ▼
           CHP Engine        Infrastructure
                │                │
                ▼                ▼
            Nginx/PHP          SSL
                │
                ▼
             WEBSITE
                │
                ▼
            www-data

    The website user should not control the hosting engine.


    72. Command Permissions

    The /usr/local/bin/hosting-* commands should normally require root.

    For example:

    sudo hosting-site-status example.com

    The future dashboard/API should also run through a controlled privileged service rather than granting unrestricted shell access.


    73. Don’t Give the Web Server sudo

    Avoid configurations such as:

    www-data ALL=(ALL) NOPASSWD: ALL

    That would effectively turn a website compromise into server compromise.

    Use a narrowly scoped privileged backend later.


    74. CHP Architecture Now

    We now have:

                     CRESIGNSYS HOSTING
                             │
                 ┌───────────┴───────────┐
                 ▼                       ▼
             COMMANDS                    CORE
                 │                       │
                 │             ┌─────────┼─────────┐
                 │             ▼         ▼         ▼
                 │          DOMAIN      DNS      NGINX
                 │             │         │         │
                 │             ▼         ▼         ▼
                 │           SSL       PHP       MYSQL
                 │                       │         │
                 │                       └────┬────┘
                 │                            ▼
                 │                        WORDPRESS
                 │
                 ├── backup
                 ├── restore
                 ├── suspend
                 └── repair

    75. The Commands Are Now Orchestrators

    For example:

    hosting-repair

    should not contain its own implementation of:

    PHP detection
    Nginx reload
    logging
    locking
    state

    Instead:

    hosting-repair
          ↓
    core libraries
          ↓
    perform repair
          ↓
    verify

    76. This Makes Future Development Faster

    Once nginx.sh contains:

    reload_nginx()

    you can use it from:

    hosting-create
    hosting-domain-add
    hosting-domain-remove
    hosting-suspend
    hosting-unsuspend
    hosting-repair
    hosting-ssl

    without rewriting the logic.


    77. Standard Result Format

    Eventually every core function should return:

    SUCCESS
    WARNING
    FAILURE

    and optionally:

    code
    message
    details

    This makes JSON output easier later.


    78. Example Internal Result

    Conceptually:

    status=FAIL
    code=DNS_WRONG
    message="Domain points to unexpected IP"

    The CLI can display:

    DNS:
    WRONG

    while the API can return:

    {
      "status": "fail",
      "code": "DNS_WRONG",
      "message": "Domain points to unexpected IP"
    }

    79. Avoid Parsing Human Output

    Don’t make:

    hosting-site-status

    parse:

    [OK] DNS

    from another command.

    Instead both use:

    check_dns()

    This is one of the most important improvements in this lesson.


    80. CHP Core Principles

    From now on, every feature should follow these rules:

    1. Validate input
    2. Load centralized configuration
    3. Acquire appropriate lock
    4. Use shared libraries
    5. Generate desired configuration
    6. Validate before activation
    7. Verify the result
    8. Update state
    9. Write audit log

    81. Current Project Structure

    The target structure is now:

    /etc/cresignsys/
    │
    ├── hosting.conf
    │
    ├── lib/
    │   ├── common.sh
    │   ├── logging.sh
    │   ├── lock.sh
    │   ├── state.sh
    │   ├── operation.sh
    │   ├── domain.sh
    │   ├── dns.sh
    │   ├── nginx.sh
    │   ├── php.sh
    │   ├── mysql.sh
    │   ├── wordpress.sh
    │   ├── ssl.sh
    │   └── backup.sh
    │
    └── templates/
        ├── nginx/
        ├── php/
        └── suspended/

    82. Operational Data

    /var/lib/cresignsys/
    └── sites/
        └── example.com/
            ├── site.conf
            ├── domains.conf
            └── state

    83. Logs

    /var/log/cresignsys/
    ├── operations.log
    ├── errors.log
    └── audit.log

    84. Website Data

    Your existing website architecture remains:

    /storage/websites/
    └── example.com/
        └── public/

    This separation is important:

    CHP metadata
          ≠
    customer website

    85. Backup Data

    /backup/cresignsys/
    └── example.com/
        ├── 2026-08-13_160000/
        ├── 2026-08-12_160000/
        └── ...

    Again:

    backup
    ≠
    live website

    86. The Four Major Data Layers

    The platform now has four clearly separated layers:

    1. CONFIGURATION
       /etc/cresignsys
    
    2. STATE
       /var/lib/cresignsys
    
    3. LIVE WEBSITE
       /storage/websites
    
    4. BACKUP
       /backup/cresignsys

    This separation is foundational.


    87. Why This Is Important

    A restore should modify:

    LIVE WEBSITE

    and selected application data.

    It should not blindly overwrite:

    CHP CORE

    A configuration repair should modify:

    CHP-generated infrastructure configuration

    not customer content.

    A backup prune should modify:

    BACKUP

    not live website data.


    88. Lesson 077 — Core Principle

    We have now moved from:

    A collection of hosting Bash scripts

    toward:

    A structured hosting platform engine.

    The architecture is:

                    CHP CORE
                       │
           ┌───────────┼───────────┐
           ▼           ▼           ▼
        Commands     Libraries   Templates
           │           │           │
           └───────────┼───────────┘
                       ▼
                  SITE STATE
                       │
              ┌────────┼────────┐
              ▼        ▼        ▼
            Nginx     PHP      MySQL
              │        │        │
              └────────┼────────┘
                       ▼
                    Website

    Next Lesson — 078

    Build the CHP Site Database

    The filesystem-based model is useful for the initial platform, but we are reaching the point where a database will make management much cleaner.

    We will design:

    sites
    domains
    site_services
    backups
    operations
    ssl_certificates

    with relationships such as:

                        sites
                          │
              ┌───────────┼────────────┐
              ▼           ▼            ▼
           domains     backups      services
              │
              ▼
            SSL

    Then commands such as:

    hosting-list
    hosting-info
    hosting-domain-list
    hosting-backup-list
    hosting-site-status

    can query structured site metadata rather than scanning directories.

    The next major architectural transition will therefore be:

    FILESYSTEM STATE ↓ CENTRAL SITE DATABASE ↓ CHP CORE ↓ CLI + FUTURE WEB CONTROL PANEL

  • CresignSys Learn — Lesson 076

    Build hosting-site-status

    We now have separate diagnostic commands:

    hosting-info DOMAIN
    hosting-dns-check DOMAIN
    hosting-health DOMAIN

    The next step is to combine them into one site status engine.

    Target:

    sudo hosting-site-status example.com

    1. Why hosting-site-status?

    An administrator should not need to run:

    hosting-info
    hosting-dns-check
    hosting-health
    hosting-ssl
    hosting-backup-list

    individually just to understand one website.

    Instead:

                        SITE STATUS
                             │
           ┌─────────────────┼─────────────────┐
           ▼                 ▼                 ▼
        DOMAIN              DNS              SERVER
           │                 │                 │
           ▼                 ▼                 ▼
         SSL               HTTP             PHP-FPM
           │                                   │
           └─────────────────┬─────────────────┘
                             ▼
                        APPLICATION
                             │
                             ▼
                          BACKUP
                             │
                             ▼
                        OVERALL STATE

    2. Target Output

    For a healthy site:

    CresignSys Site Status
    ======================
    
    Domain:
    example.com
    
    Overall:
    HEALTHY
    
    SERVICE
      State:       ACTIVE
    
    DNS
      IPv4:        CORRECT
      IPv6:        NOT CONFIGURED
    
    WEB
      Nginx:       OK
      HTTP:        OK
      HTTPS:       OK
    
    SSL
      Status:      ACTIVE
      Certificate: VALID
    
    PHP
      Version:     8.3
      PHP-FPM:     OK
      Socket:      OK
    
    DATABASE
      MySQL:       OK
      WordPress DB: OK
    
    APPLICATION
      WordPress:   OK
    
    BACKUP
      Status:      HEALTHY
      Last Backup: 2026-08-13 16:00
    
    FILES
      Web Root:    OK
      Ownership:   OK
    
    Overall Health:
    HEALTHY

    3. Status Is Not the Same as Health

    This distinction is important.

    A site can be:

    SERVICE_STATE=SUSPENDED
    HEALTH_STATE=HEALTHY

    That means:

    The infrastructure is working, but the administrator intentionally suspended public access.

    Therefore don’t collapse everything into one Boolean.


    4. Site State Model

    Use:

    SERVICE_STATE

    with:

    ACTIVE
    SUSPENDED
    PROVISIONING
    WAITING_DNS
    ERROR
    DELETING
    DELETED

    5. Health State Model

    Use:

    HEALTH_STATE

    with:

    HEALTHY
    DEGRADED
    CRITICAL
    UNKNOWN

    6. Example

    Healthy active site

    SERVICE_STATE=ACTIVE
    HEALTH_STATE=HEALTHY

    Suspended site

    SERVICE_STATE=SUSPENDED
    HEALTH_STATE=HEALTHY

    DNS waiting

    SERVICE_STATE=WAITING_DNS
    HEALTH_STATE=DEGRADED

    Database failure

    SERVICE_STATE=ACTIVE
    HEALTH_STATE=CRITICAL

    This is much more expressive.


    7. Status Categories

    The unified checker should inspect:

    1. Site metadata
    2. Domain
    3. DNS
    4. Nginx
    5. HTTP
    6. HTTPS
    7. SSL
    8. PHP-FPM
    9. PHP socket
    10. Database
    11. WordPress
    12. Filesystem
    13. Backup
    14. Service state

    8. Don’t Make Every Check Fatal

    This is a common mistake.

    Suppose:

    DNS = PENDING

    The script should not necessarily terminate.

    It should continue:

    DNS      PENDING
    HTTP     UNKNOWN
    HTTPS    UNKNOWN
    PHP      OK
    Database OK
    WordPress OK

    Then classify:

    Overall:
    WAITING_DNS

    This gives much more useful information.


    9. Check Execution Model

    Use:

    RUN CHECK
       │
       ├── PASS
       ├── WARN
       ├── FAIL
       └── UNKNOWN

    Don’t immediately:

    exit 1

    after the first failure.


    10. Check Result Structure

    Each check should conceptually return:

    NAME
    STATUS
    MESSAGE
    SEVERITY

    Example:

    DNS
    STATUS=FAIL
    MESSAGE=Domain points to 198.51.100.20
    SEVERITY=CRITICAL

    11. Severity Levels

    Use:

    OK
    WARNING
    CRITICAL
    UNKNOWN

    Example:

    DNS:
    OK
    
    SSL:
    WARNING
    
    Database:
    CRITICAL

    Overall health becomes:

    CRITICAL

    because the database is unavailable.


    12. Overall Health Algorithm

    A simple first version:

    If any critical check fails:
        CRITICAL
    
    Else if any warning exists:
        DEGRADED
    
    Else:
        HEALTHY

    If checks cannot be performed:

    UNKNOWN

    where appropriate.


    13. Example

    DNS          OK
    Nginx        OK
    HTTP         OK
    HTTPS        OK
    SSL          WARNING
    PHP          OK
    Database     OK
    WordPress    OK
    Backup       OK

    Result:

    HEALTH_STATE=DEGRADED

    14. Example — Critical

    DNS          OK
    Nginx        OK
    HTTP         500
    HTTPS        500
    SSL          OK
    PHP          OK
    Database     FAIL
    WordPress    FAIL

    Result:

    HEALTH_STATE=CRITICAL

    15. Example — Suspended

    Service      SUSPENDED
    DNS          OK
    Nginx        OK
    HTTPS        503
    SSL          OK
    Database     OK
    WordPress    OK

    Don’t report:

    CRITICAL

    if 503 is intentionally produced by the suspension configuration.

    Report:

    SERVICE_STATE=SUSPENDED
    HEALTH_STATE=HEALTHY

    16. Create Script

    Create:

    sudo nano /usr/local/bin/hosting-site-status

    Start:

    #!/usr/bin/env bash
    
    set -Eeuo pipefail

    17. Arguments

    if [[ $# -ne 1 ]]; then
        echo "Usage: hosting-site-status DOMAIN"
        exit 1
    fi
    
    DOMAIN="$1"

    Later we can add:

    --json
    --quiet
    --verbose

    18. Load Site Configuration

    SITE_ROOT="/var/lib/cresignsys/sites/${DOMAIN}"
    SITE_CONF="${SITE_ROOT}/site.conf"
    
    if [[ ! -f "$SITE_CONF" ]]; then
        echo "ERROR: Site not found: $DOMAIN"
        exit 1
    fi
    
    source "$SITE_CONF"

    19. Don’t Trust source Forever

    There is an architectural warning here.

    If:

    site.conf

    contains data controlled by an untrusted user, blindly doing:

    source site.conf

    can execute arbitrary shell commands.

    For an administrator-controlled system this may be acceptable during early development, but a production multi-tenant platform should eventually use:

    INI
    JSON
    SQLite
    database

    with safe parsing.


    20. Site State

    Read:

    SERVICE_STATE

    If missing:

    SERVICE_STATE=UNKNOWN

    Then display:

    SERVICE
      State: ACTIVE

    21. Domain Check

    Verify:

    DOMAIN

    matches the requested site.

    Don’t allow:

    hosting-site-status example.com

    to accidentally load:

    shop.com

    metadata.


    22. DNS Check

    Call the DNS checking logic.

    Preferably don’t execute:

    hosting-dns-check "$DOMAIN"

    and parse its human-readable output.

    Instead, create a reusable library:

    /etc/cresignsys/lib/dns.sh

    For example:

    check_dns()

    returns structured variables.


    23. Why Libraries Matter

    Without libraries:

    hosting-health
    hosting-site-status
    hosting-ssl
    hosting-domain-list

    may all implement DNS checking differently.

    That causes inconsistent results.

    Better:

                  dns.sh
                    │
           ┌────────┼────────┐
           ▼        ▼        ▼
        health    status    ssl

    24. Create Common Library

    Directory:

    sudo mkdir -p /etc/cresignsys/lib

    Then:

    sudo nano /etc/cresignsys/lib/dns.sh

    Eventually:

    dns.sh
    domain.sh
    nginx.sh
    php.sh
    mysql.sh
    wordpress.sh
    backup.sh
    logging.sh
    lock.sh

    25. This Changes the Architecture

    Instead of:

    10 scripts
    ×
    10 duplicated functions

    we get:

    10 commands
          │
          ▼
    shared libraries
          │
          ▼
    common behavior

    This is a major improvement.


    26. Nginx Check

    Check service:

    systemctl is-active --quiet nginx

    If active:

    Nginx:
    OK

    But this alone isn’t enough.

    Nginx can be running while the site’s server block is broken.


    27. Site-Specific Nginx Check

    Verify the domain exists in the loaded Nginx configuration.

    For example:

    nginx -T

    and inspect the generated configuration.

    Don’t repeatedly run expensive full configuration dumps for every site on a large server.

    Eventually maintain configuration metadata.


    28. Better Nginx Check

    Check:

    site config exists
    server_name exists
    configuration enabled
    nginx syntax valid

    Then:

    Nginx:
    OK

    29. HTTP Check

    Use:

    curl -I --max-time 10 "http://${DOMAIN}"

    Possible result:

    200
    301
    302
    403
    404
    500
    503

    Not every non-200 response is necessarily a server failure.


    30. HTTP Interpretation

    For an active site:

    200 → OK
    301 → OK
    302 → OK
    403 → WARNING/possibly application policy
    404 → WARNING
    500 → CRITICAL
    502 → CRITICAL
    503 → CRITICAL

    For a suspended site:

    503 → EXPECTED

    31. HTTPS Check

    curl -Ik --max-time 10 "https://${DOMAIN}"

    The -k option is useful for diagnostic separation when you want to test HTTP behavior even if certificate validation fails.

    But don’t use -k to declare SSL healthy.

    Certificate validation needs its own check.


    32. SSL Check

    Use:

    openssl s_client

    or an appropriate certificate inspection method.

    Check:

    certificate exists
    certificate not expired
    hostname covered
    chain valid

    33. SSL States

    ACTIVE
    EXPIRING_SOON
    EXPIRED
    MISMATCH
    MISSING
    ERROR

    For example:

    SSL:
    EXPIRING_SOON

    should be:

    WARNING

    while:

    SSL:
    EXPIRED

    is:

    CRITICAL

    34. PHP-FPM Check

    Find the configured PHP version:

    PHP_VERSION=8.3

    Then:

    systemctl is-active php8.3-fpm

    Expected:

    PHP-FPM:
    OK

    35. PHP Socket Check

    If your Nginx configuration uses:

    /run/php/php8.3-fpm-example.com.sock

    verify the socket exists.

    For example:

    PHP Socket:
    OK

    If:

    Nginx → missing socket

    you can get:

    502 Bad Gateway

    even though PHP-FPM itself is running.


    36. Database Check

    Check MySQL:

    systemctl is-active --quiet mysql

    Then test the site’s database credentials.

    Don’t just check:

    MySQL service = running

    because the specific site’s database could still be inaccessible.


    37. WordPress Database Check

    Use:

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT" db check

    Expected:

    Database:
    OK

    38. WordPress Check

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT" core is-installed

    Then optionally:

    sudo -u "$SITE_USER" \
    wp --path="$WEB_ROOT" core verify-checksums

    Be aware that checksum verification may not be appropriate for sites with modified core files.


    39. Filesystem Check

    Verify:

    WEB_ROOT exists
    wp-config.php exists
    permissions reasonable
    owner correct
    disk available

    Example:

    FILES
      Web Root: OK
      Ownership: OK
      Disk: OK

    40. Disk Space

    A site can be perfectly configured but fail because:

    DISK = 100%

    Check:

    df -P "$WEB_ROOT"

    Use thresholds.

    For example:

    < 80%  → OK
    80-90% → WARNING
    > 90%  → CRITICAL

    These are policy examples and can be configurable.


    41. Inode Usage

    Disk capacity isn’t the only problem.

    A server can have:

    Disk:
    50% used

    but:

    Inodes:
    100% used

    Then new files cannot be created.

    Check:

    df -Pi "$WEB_ROOT"

    This should eventually be part of site health.


    42. Backup Check

    Read the backup inventory.

    Check:

    backup enabled?
    last successful backup?
    backup age?
    backup verification status?

    Example:

    BACKUP
      Enabled:     YES
      Last:        2 hours ago
      Status:      HEALTHY

    43. Backup Warning

    Suppose:

    Last backup:
    10 days ago

    while policy requires:

    daily

    Then:

    Backup:
    WARNING

    Overall:

    DEGRADED

    44. Missing Backup

    If:

    BACKUP_ENABLED=true

    but:

    no valid backup exists

    then:

    Backup:
    CRITICAL

    depending on your hosting policy.


    45. Status Summary

    At the bottom:

    ================================
    
    SERVICE:
    ACTIVE
    
    HEALTH:
    HEALTHY
    
    RESULT:
    HEALTHY

    Or:

    SERVICE:
    ACTIVE
    
    HEALTH:
    CRITICAL
    
    RESULT:
    CRITICAL

    46. Machine-Readable Mode

    This is extremely important for the future control panel.

    Add:

    sudo hosting-site-status example.com --json

    Output:

    {
      "domain": "example.com",
      "service_state": "ACTIVE",
      "health_state": "HEALTHY",
      "dns": "CORRECT",
      "nginx": "OK",
      "http": "OK",
      "https": "OK",
      "ssl": "ACTIVE",
      "php": "OK",
      "database": "OK",
      "wordpress": "OK",
      "backup": "HEALTHY"
    }

    47. Why JSON Matters

    A future dashboard can consume:

    hosting-site-status --json

    without parsing:

    [OK] Nginx
    [OK] PHP

    text.

    This creates a clean boundary:

    CHP Engine
         ↓
    JSON
         ↓
    Dashboard

    48. Don’t Build the Dashboard Yet

    First make the CLI engine reliable.

    The future dashboard can then be:

    Web UI
       ↓
    API
       ↓
    CHP Engine

    rather than duplicating all hosting logic in PHP/JavaScript.


    49. Exit Codes

    The command should also return useful exit codes.

    For example:

    0 = HEALTHY
    1 = DEGRADED
    2 = CRITICAL
    3 = UNKNOWN

    Then automation can do:

    if hosting-site-status example.com --quiet; then
        echo "Healthy"
    fi

    50. Why Exit Codes Matter

    Monitoring systems don’t read pretty terminal output.

    They need:

    0
    1
    2

    to decide whether an alert should be triggered.

    This allows later integration with monitoring systems.


    51. Quiet Mode

    Add:

    sudo hosting-site-status example.com --quiet

    Output:

    HEALTHY

    or nothing except an error.

    This is useful for scripts.


    52. Verbose Mode

    Add:

    sudo hosting-site-status example.com --verbose

    to show:

    DNS query
    resolver
    IP addresses
    TTL
    Nginx server block
    PHP socket
    database connection
    certificate details
    backup age
    disk usage

    53. Normal Mode Should Stay Simple

    Normal:

    hosting-site-status example.com

    should be readable.

    Don’t dump 200 lines of technical information by default.


    54. Example — Healthy

    CresignSys Site Status
    ======================
    
    example.com
    
    SERVICE
      State:       ACTIVE
    
    DNS
      Status:      CORRECT
    
    WEB
      Nginx:       OK
      HTTP:        OK
      HTTPS:       OK
    
    SSL
      Status:      ACTIVE
    
    PHP
      PHP-FPM:     OK
      Socket:      OK
    
    DATABASE
      MySQL:       OK
      WordPress:   OK
    
    FILES
      Web Root:    OK
      Ownership:   OK
      Disk:        OK
    
    BACKUP
      Status:      HEALTHY
      Last:        2 hours ago
    
    --------------------------------
    OVERALL: HEALTHY

    55. Example — Waiting for DNS

    CresignSys Site Status
    ======================
    
    example.com
    
    SERVICE
      State:       WAITING_DNS
    
    DNS
      Status:      PENDING
    
    WEB
      Nginx:       OK
      HTTP:        UNKNOWN
      HTTPS:       UNKNOWN
    
    SSL
      Status:      PENDING
    
    PHP
      PHP-FPM:     OK
    
    DATABASE
      MySQL:       OK
    
    APPLICATION
      WordPress:   OK
    
    BACKUP
      Status:      HEALTHY
    
    --------------------------------
    OVERALL: WAITING_DNS

    This is much more useful than simply saying:

    ERROR

    56. Example — Database Failure

    example.com
    
    SERVICE
      State:       ACTIVE
    
    DNS
      Status:      CORRECT
    
    WEB
      Nginx:       OK
      HTTP:        500
      HTTPS:       500
    
    PHP
      PHP-FPM:     OK
    
    DATABASE
      MySQL:       FAIL
    
    APPLICATION
      WordPress:   FAIL
    
    BACKUP
      Status:      HEALTHY
    
    --------------------------------
    OVERALL: CRITICAL

    57. Example — Suspended

    example.com
    
    SERVICE
      State:       SUSPENDED
    
    DNS
      Status:      CORRECT
    
    WEB
      Nginx:       OK
      HTTPS:       EXPECTED_503
    
    SSL
      Status:      ACTIVE
    
    PHP
      PHP-FPM:     OK
    
    DATABASE
      MySQL:       OK
    
    APPLICATION
      WordPress:   OK
    
    BACKUP
      Status:      HEALTHY
    
    --------------------------------
    OVERALL: SUSPENDED

    58. Important: Expected Failures

    This is a major monitoring principle.

    A suspended site returning:

    503

    isn’t necessarily a failure.

    Similarly:

    HTTP 301

    isn’t necessarily a failure.

    The checker needs context.

    HTTP result
    +
    SERVICE_STATE
    +
    DOMAIN_TYPE

    determines the meaning.


    59. Redirect Domain

    For:

    old.example.com

    the expected HTTP result might be:

    301

    Therefore:

    301 = OK

    for a redirect domain.

    This is why checks must understand domain type.


    60. Alias Domain

    For:

    www.example.com

    expected:

    200

    or perhaps:

    301 → example.com

    depending on your canonical URL policy.

    Again, the status engine needs metadata.


    61. Domain Policy

    Add:

    CANONICAL=true
    REDIRECT_TARGET=

    or equivalent domain metadata.

    Then health checks know what response is expected.


    62. Status Engine Architecture

    The internal model becomes:

                       SITE STATUS
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
            Metadata      Domain       Service
              │            │            │
              ▼            ▼            ▼
           Policy         DNS          State
                           │
                           ▼
                        Web Layer
                           │
                 ┌─────────┼─────────┐
                 ▼         ▼         ▼
               HTTP       HTTPS      SSL
                           │
                           ▼
                      Application
                           │
                  ┌────────┼────────┐
                  ▼        ▼        ▼
                 PHP       DB     WordPress
                           │
                           ▼
                        Backup
                           │
                           ▼
                     Health Engine

    63. Shared Libraries

    At this stage, create:

    /etc/cresignsys/lib/
    ├── common.sh
    ├── domain.sh
    ├── dns.sh
    ├── nginx.sh
    ├── php.sh
    ├── mysql.sh
    ├── wordpress.sh
    ├── backup.sh
    ├── ssl.sh
    ├── logging.sh
    └── lock.sh

    This is a major architectural milestone.


    64. common.sh

    Put shared functions such as:

    log_info()
    log_warn()
    log_error()
    die()
    require_root()
    command_exists()

    there.


    65. domain.sh

    Functions:

    validate_domain()
    normalize_domain()
    domain_exists()
    site_exists()

    66. dns.sh

    Functions:

    get_a_records()
    get_aaaa_records()
    get_cname()
    get_nameservers()
    check_dns()

    67. nginx.sh

    Functions:

    nginx_is_running()
    nginx_config_valid()
    site_config_exists()
    reload_nginx()

    68. php.sh

    Functions:

    php_fpm_is_running()
    php_socket_exists()
    get_php_version()

    69. mysql.sh

    Functions:

    mysql_is_running()
    database_exists()
    database_connection_test()

    70. wordpress.sh

    Functions:

    wordpress_is_installed()
    wordpress_db_check()
    wordpress_core_check()

    71. backup.sh

    Functions:

    latest_backup()
    backup_exists()
    backup_is_valid()
    backup_age()

    72. ssl.sh

    Functions:

    certificate_exists()
    certificate_expiry()
    certificate_matches_domain()

    73. logging.sh

    Functions:

    audit_log()
    operation_log()

    74. lock.sh

    Functions:

    acquire_site_lock()
    release_site_lock()

    This prevents duplicate locking logic across scripts.


    75. Command Layer

    Now the commands become relatively thin:

    hosting-create
          ↓
    common libraries
    
    hosting-health
          ↓
    common libraries
    
    hosting-site-status
          ↓
    common libraries

    This is a much cleaner architecture than having every command contain thousands of lines of Bash.


    76. Future API

    Once the CLI is structured this way:

    CHP Core
       │
       ├── CLI
       │
       ├── API
       │
       └── Dashboard

    can all use the same underlying state and logic.


    77. The Future Control Panel

    The dashboard can eventually display:

    CresignSys Hosting
    ────────────────────────────────
    
    Sites       42
    Healthy     37
    Degraded     3
    Critical     1
    Suspended    1
    
    DNS Issues   2
    SSL Issues   1
    Backup Issues 1

    Click:

    example.com

    and get the complete status.


    78. Monitoring

    The status engine also allows scheduled monitoring:

    every 5 minutes
          ↓
    hosting-site-status
          ↓
    problem?
          ↓
    alert

    For example:

    Database failure detected
    Domain: example.com
    Time: 18:42

    79. Avoid Alert Spam

    If a site remains broken for:

    30 minutes

    don’t send:

    360 alerts

    Instead use state transitions:

    HEALTHY
       ↓
    CRITICAL

    send alert.

    Then:

    CRITICAL
       ↓
    CRITICAL

    don’t repeatedly alert unless configured.

    Then:

    CRITICAL
       ↓
    HEALTHY

    send recovery notification.


    80. Status History

    Eventually store:

    site_id
    timestamp
    health_state
    reason

    Example:

    18:00 HEALTHY
    18:42 CRITICAL DATABASE
    18:47 CRITICAL DATABASE
    18:53 HEALTHY

    This creates a basic uptime history.


    81. Uptime Percentage

    Once history exists:

    total monitoring time
    -
    downtime

    can calculate:

    99.9%
    99.95%
    99.99%

    This can eventually become a hosting dashboard metric.


    82. Lesson 076 — Core Principle

    hosting-site-status should become the single source of operational truth for a website.

    It combines:

    DOMAIN
    DNS
    NGINX
    HTTP
    HTTPS
    SSL
    PHP
    DATABASE
    WORDPRESS
    FILES
    BACKUP
    SERVICE STATE

    and converts those individual signals into:

    HEALTHY
    DEGRADED
    CRITICAL
    SUSPENDED
    WAITING_DNS

    83. Updated CHP Command Set

    hosting-create DOMAIN
    
    hosting-list
    hosting-info DOMAIN
    hosting-site-status DOMAIN
    hosting-health DOMAIN
    
    hosting-repair DOMAIN --php
    hosting-repair DOMAIN --nginx
    hosting-repair DOMAIN --permissions
    hosting-repair DOMAIN --wordpress
    
    hosting-ssl DOMAIN
    
    hosting-backup DOMAIN
    hosting-backup-list DOMAIN
    hosting-backup-verify DOMAIN BACKUP_ID
    hosting-backup-prune DOMAIN --dry-run
    hosting-backup-prune DOMAIN --execute
    hosting-restore DOMAIN BACKUP_ID
    
    hosting-suspend DOMAIN
    hosting-unsuspend DOMAIN
    
    hosting-domain-add DOMAIN
    hosting-domain-add DOMAIN --alias TARGET
    hosting-domain-add DOMAIN --redirect TARGET
    hosting-domain-list DOMAIN
    hosting-domain-remove DOMAIN
    
    hosting-dns-check DOMAIN

    Next Lesson — 077

    Build the Shared CHP Core Library

    We have reached the point where continuing to add standalone Bash scripts will create duplicated code.

    The next lesson should therefore consolidate the platform into:

    /etc/cresignsys/
    ├── config/
    ├── lib/
    │   ├── common.sh
    │   ├── domain.sh
    │   ├── dns.sh
    │   ├── nginx.sh
    │   ├── php.sh
    │   ├── mysql.sh
    │   ├── wordpress.sh
    │   ├── ssl.sh
    │   ├── backup.sh
    │   ├── logging.sh
    │   └── lock.sh
    ├── templates/
    │   ├── nginx/
    │   ├── php/
    │   └── suspended/
    └── sites/

    and:

    /usr/local/bin/
    ├── hosting-create
    ├── hosting-list
    ├── hosting-info
    ├── hosting-health
    ├── hosting-site-status
    ├── hosting-repair
    ├── hosting-ssl
    ├── hosting-backup
    ├── hosting-restore
    ├── hosting-suspend
    ├── hosting-unsuspend
    ├── hosting-domain-add
    ├── hosting-domain-list
    ├── hosting-domain-remove
    └── hosting-dns-check

    The goal is to move from a collection of scripts to a real CresignSys Hosting Platform core with shared validation, locking, logging, configuration, state management, and reusable service functions.

  • CresignSys Learn — Lesson 075

    Build hosting-dns-check

    We now have the domain layer. The next step is to verify whether domains actually point to the CresignSys server.

    Target command:

    sudo hosting-dns-check example.com

    The important distinction is:

    Registering a domain in CHP does not mean its DNS is correctly configured.


    1. DNS Architecture

    The complete request path is now:

    USER
      │
      ▼
    DOMAIN
      │
      ▼
    DNS
      │
      ▼
    SERVER IP
      │
      ▼
    NGINX
      │
      ▼
    HTTPS
      │
      ▼
    PHP-FPM
      │
      ▼
    WORDPRESS

    If DNS is wrong, everything after DNS can be perfectly configured and the website will still not work.


    2. What hosting-dns-check Must Answer

    For:

    sudo hosting-dns-check example.com

    we want to know:

    Domain:
    example.com
    
    A:
    203.0.113.10
    
    AAAA:
    None
    
    Expected IPv4:
    203.0.113.10
    
    Result:
    DNS CORRECT

    3. DNS States

    Use explicit states:

    CORRECT
    PENDING
    WRONG
    MULTIPLE
    ERROR

    CORRECT

    DNS points to the expected server.

    PENDING

    DNS doesn’t exist yet.

    WRONG

    DNS exists but points somewhere else.

    MULTIPLE

    Multiple records exist and at least one isn’t expected.

    ERROR

    DNS query failed.


    4. Why Multiple Matters

    Suppose:

    example.com

    has:

    A → 203.0.113.10
    A → 198.51.100.20

    Your server is:

    203.0.113.10

    Some users may reach your server while others reach the other server.

    Therefore:

    DNS = MULTIPLE

    may be more accurate than simply:

    DNS = CORRECT

    5. Read Expected Server IP

    Don’t hard-code:

    SERVER_IP=...

    into every domain.

    Put server identity in centralized configuration:

    /etc/cresignsys/hosting.conf

    For example:

    SERVER_IPV4="YOUR_SERVER_IPV4"
    SERVER_IPV6="YOUR_SERVER_IPV6"

    Only define IPv6 if the server actually supports it.


    6. Why IPv6 Matters

    A domain might have:

    A     → IPv4
    AAAA  → IPv6

    A browser may prefer IPv6.

    If:

    AAAA → wrong server

    while:

    A → correct server

    some users can still see the wrong website.

    Therefore checking only the A record is incomplete.


    7. Query A Record

    Use:

    dig +short A example.com

    Example:

    203.0.113.10

    Store:

    DNS_IPV4="203.0.113.10"

    8. Query AAAA

    dig +short AAAA example.com

    If there is no result:

    AAAA:
    NONE

    That isn’t necessarily an error.

    It simply means IPv6 isn’t configured for that hostname.


    9. Query CNAME

    dig +short CNAME www.example.com

    For:

    www.example.com

    you might see:

    example.com.

    This means:

    www.example.com
           ↓
    CNAME
           ↓
    example.com
           ↓
    A
           ↓
    server

    10. Don’t Treat CNAME as an Error

    For:

    www.example.com

    the correct configuration could be:

    CNAME www → example.com

    instead of:

    A www → server

    Both architectures can be valid.

    Therefore the DNS checker must understand record relationships.


    11. DNS Resolution

    Example:

    www.example.com
           │
           ▼
    CNAME example.com
           │
           ▼
    A 203.0.113.10

    The final resolved address is:

    203.0.113.10

    So the checker should distinguish:

    record type

    from:

    final destination

    12. Use a Resolver

    A DNS answer depends on the resolver.

    For troubleshooting, you can compare:

    dig example.com

    with:

    dig @1.1.1.1 example.com

    and:

    dig @8.8.8.8 example.com

    This can reveal resolver-specific differences or propagation issues.


    13. Don’t Assume DNS Changes Are Instant

    Suppose the user changes:

    A example.com

    from:

    OLD_IP

    to:

    NEW_IP

    Different recursive resolvers may continue returning the old record until the previous TTL expires.

    Therefore:

    DNS changed

    does not necessarily mean:

    everyone sees new DNS immediately

    14. DNS TTL

    Query:

    dig example.com

    You’ll see a TTL field in the response.

    Conceptually:

    TTL = 300

    means recursive resolvers may cache the answer for approximately 300 seconds.

    The actual propagation behavior can be more complex because of caching layers.


    15. hosting-dns-check Should Report TTL

    Example:

    A Record:
    203.0.113.10
    
    TTL:
    300 seconds

    This helps administrators understand why a recent DNS change may still be propagating.


    16. Create the Script

    Create:

    sudo nano /usr/local/bin/hosting-dns-check

    Start:

    #!/usr/bin/env bash
    
    set -Eeuo pipefail

    17. Require Root

    if [[ "$EUID" -ne 0 ]]; then
        echo "ERROR: Run with sudo."
        exit 1
    fi

    18. Require Domain

    if [[ $# -ne 1 ]]; then
        echo "Usage: hosting-dns-check DOMAIN"
        exit 1
    fi
    
    DOMAIN="$1"

    19. Validate Domain

    Reuse the same hostname-validation function from:

    hosting-domain-add

    Don’t implement five different domain validators.

    Eventually create:

    /etc/cresignsys/lib/domain.sh

    with reusable functions.


    20. Load Configuration

    source /etc/cresignsys/hosting.conf

    Then:

    EXPECTED_IPV4="${SERVER_IPV4:-}"
    EXPECTED_IPV6="${SERVER_IPV6:-}"

    21. Query IPv4

    mapfile -t IPV4_RECORDS < <(
        dig +short A "$DOMAIN"
    )

    Now you may have:

    203.0.113.10

    or:

    203.0.113.10
    198.51.100.20

    22. Compare IPv4

    If:

    EXPECTED_IPV4=203.0.113.10

    and:

    IPV4_RECORDS:
    203.0.113.10

    then:

    IPv4:
    CORRECT

    If:

    IPV4_RECORDS:
    198.51.100.20

    then:

    IPv4:
    WRONG

    23. Multiple IPv4 Records

    If:

    203.0.113.10
    198.51.100.20

    then:

    IPv4:
    MULTIPLE

    unless your platform intentionally supports multiple origin IPs.


    24. Multi-Server Hosting

    Later CHP may support:

    Server A
    Server B
    Server C

    Then multiple A records might be intentional.

    For now, if CHP has one origin server:

    multiple unexpected A records

    should generate a warning.


    25. IPv6 Check

    Run:

    mapfile -t IPV6_RECORDS < <(
        dig +short AAAA "$DOMAIN"
    )

    Then compare against:

    SERVER_IPV6

    If IPv6 isn’t configured on the server:

    AAAA:
    UNEXPECTED

    if the domain points somewhere else.


    26. Important IPv6 Scenario

    Suppose:

    A:
    YOUR_SERVER
    
    AAAA:
    OTHER_SERVER

    Then:

    DNS Status:
    WRONG

    even though:

    A:
    CORRECT

    because IPv6 clients may reach the wrong machine.


    27. CNAME Handling

    For an alias:

    www.example.com

    check:

    dig +short CNAME www.example.com

    If:

    example.com.

    then recursively inspect:

    example.com

    to determine the final IP.


    28. DNS Checker Output

    A useful result:

    CresignSys DNS Check
    ====================
    
    Domain:
    www.example.com
    
    CNAME:
    www.example.com → example.com
    
    A:
    203.0.113.10
    
    Expected:
    203.0.113.10
    
    AAAA:
    None
    
    TTL:
    300
    
    Result:
    DNS CORRECT

    29. Wrong DNS Output

    CresignSys DNS Check
    ====================
    
    Domain:
    example.com
    
    A:
    198.51.100.20
    
    Expected:
    203.0.113.10
    
    Result:
    DNS WRONG

    30. Pending DNS

    If:

    dig +short A example.com

    returns nothing:

    A:
    NONE

    show:

    Result:
    DNS PENDING

    31. Multiple Records

    A:
    203.0.113.10
    198.51.100.20
    
    Expected:
    203.0.113.10
    
    Result:
    DNS MULTIPLE

    32. DNS Error

    If the resolver cannot be reached:

    Result:
    DNS ERROR

    This is different from:

    DNS PENDING

    because:

    PENDING

    means there is no record.

    ERROR

    means the check itself failed.


    33. Domain-Specific Expected DNS

    Instead of assuming every hostname should point directly to:

    SERVER_IPV4

    your domain registry can eventually contain:

    DNS_MODE=A

    or:

    DNS_MODE=CNAME

    For example:

    www.example.com
    DNS_MODE=CNAME
    DNS_TARGET=example.com

    34. DNS Requirements

    hosting-domain-list can eventually show:

    DOMAIN                 DNS
    ---------------------------------
    example.com             CORRECT
    www.example.com         CORRECT
    old-example.com         PENDING

    35. hosting-domain-add Integration

    After:

    sudo hosting-domain-add www.example.com --alias example.com

    the system can output:

    DNS:
    PENDING
    
    Required:
    CNAME www → example.com

    Then:

    sudo hosting-dns-check www.example.com

    will tell you when the DNS is ready.


    36. Don’t Automatically Modify DNS

    Unless CHP is actually operating the authoritative DNS service for the domain, don’t try to change DNS records from the hosting server.

    The hosting server can:

    CHECK DNS

    but may not be able to:

    CHANGE DNS

    37. DNS Provider Integration

    Later, CHP can support integrations such as:

    DNS Provider
         │
         ├── Create A
         ├── Create AAAA
         ├── Create CNAME
         └── Create TXT

    But that requires provider-specific API credentials and configuration.

    Keep the base DNS checker provider-independent.


    38. DNS vs Domain Registrar

    Another important distinction:

    Domain Registrar
            │
            ▼
    Domain ownership
            │
            ▼
    Nameservers
            │
            ▼
    DNS Provider
            │
            ▼
    A / AAAA / CNAME

    Your hosting platform normally doesn’t need to be the registrar.


    39. Nameserver Check

    You can inspect:

    dig +short NS example.com

    Example:

    ns1.provider.com.
    ns2.provider.com.

    This tells you which DNS infrastructure is authoritative.


    40. Why Check Nameservers?

    Suppose the customer says:

    I added the DNS record.

    But the domain uses:

    ns1.oldprovider.com

    while they modified records at another provider.

    The changes won’t affect the authoritative DNS.

    Therefore:

    NS

    is useful diagnostic information.


    41. DNS Delegation

    The full hierarchy:

    Root
     ↓
    .com
     ↓
    example.com
     ↓
    Authoritative nameservers
     ↓
    A / AAAA / CNAME

    If delegation is wrong:

    DNS configuration at the expected provider

    may never be seen by users.


    42. hosting-dns-check --verbose

    Eventually support:

    sudo hosting-dns-check example.com --verbose

    Output:

    Domain:
    example.com
    
    Nameservers:
    ns1.provider.com
    ns2.provider.com
    
    A:
    203.0.113.10
    
    AAAA:
    None
    
    TTL:
    300
    
    Expected IPv4:
    203.0.113.10
    
    Result:
    CORRECT

    43. Resolver Comparison

    For troubleshooting:

    dig @1.1.1.1 example.com
    dig @8.8.8.8 example.com

    You may see:

    Resolver 1:
    NEW IP
    
    Resolver 2:
    OLD IP

    This suggests caching/propagation differences rather than necessarily a server problem.


    44. DNS Health State

    Add to domain metadata:

    DNS_STATE=CORRECT

    Possible values:

    PENDING
    CORRECT
    WRONG
    MULTIPLE
    ERROR

    45. Domain Health Matrix

    Now each domain can have:

    DNS
    SSL
    HTTP
    HTTPS

    For example:

    example.com
    
    DNS:
    CORRECT
    
    HTTP:
    OK
    
    HTTPS:
    OK
    
    SSL:
    ACTIVE

    46. Combined Domain Check

    Eventually:

    sudo hosting-domain-health example.com

    can perform:

    DNS
     ↓
    HTTP
     ↓
    HTTPS
     ↓
    SSL

    This will complement:

    hosting-health

    which checks the whole application stack.


    47. Difference Between DNS Health and Site Health

    DNS health

    Is the hostname pointing to the right place?

    Site health

    Is the application working after reaching the server?

    Example:

    DNS ✓
    HTTP ✓
    PHP ✗

    The domain is correctly configured, but the application is broken.


    48. Complete Diagnostic Chain

    DNS
     │
     ├── FAIL → DNS problem
     │
     ▼
    Nginx
     │
     ├── FAIL → web server problem
     │
     ▼
    PHP-FPM
     │
     ├── FAIL → PHP problem
     │
     ▼
    WordPress
     │
     ├── FAIL → application problem
     │
     ▼
    Database
     │
     ├── FAIL → database problem
     │
     ▼
    HEALTHY

    This is the foundation for automatic diagnostics.


    49. DNS + SSL

    The SSL command should eventually call:

    hosting-dns-check

    before requesting a certificate.

    So:

    hosting-ssl example.com

    becomes:

    DNS check
       ↓
    HTTP check
       ↓
    certificate

    This reduces failed ACME requests.


    50. DNS + Provisioning

    The full provisioning process can become:

    hosting-create
          ↓
    site created
          ↓
    domain registered
          ↓
    DNS instructions
          ↓
    hosting-dns-check
          ↓
    DNS correct
          ↓
    hosting-ssl
          ↓
    HTTPS
          ↓
    health
          ↓
    ACTIVE

    51. Pending Provisioning

    This suggests a useful state:

    SITE_STATUS=WAITING_DNS

    Instead of:

    SITE_STATUS=ERROR

    because DNS may simply not have been configured yet.


    52. Example

    After:

    sudo hosting-create example.com

    the system may show:

    Site:
    CREATED
    
    DNS:
    PENDING
    
    Required:
    A example.com → SERVER_IP
    
    SSL:
    PENDING DNS
    
    Application:
    READY

    This is much clearer than saying:

    ERROR

    53. DNS Instructions

    The platform can automatically display:

    DNS Records
    ===========
    
    A
    Name: @
    Value: YOUR_SERVER_IP
    
    A
    Name: www
    Value: YOUR_SERVER_IP

    Or if using CNAME:

    CNAME
    Name: www
    Target: example.com

    54. Avoid Requiring Both A and CNAME for Same Host

    A hostname cannot normally have both:

    CNAME www → example.com

    and:

    A www → server

    at the same time.

    Your DNS instruction generator must choose one valid configuration.


    55. Apex Domain

    For:

    example.com

    the root/apex domain usually uses:

    A

    or an appropriate provider-specific alias/flattening mechanism.

    A standard CNAME at the zone apex is generally not supported in traditional DNS.

    This is why your DNS instruction generator should distinguish:

    apex

    from:

    subdomain

    56. www Recommendation

    A simple architecture:

    example.com
       A → SERVER
    
    www.example.com
       CNAME → example.com

    Then your Nginx configuration handles both.


    57. But CDN/Proxy Changes Things

    If the domain uses a CDN or reverse proxy:

    Browser
     ↓
    CDN
     ↓
    Server

    DNS may intentionally point to the CDN rather than directly to your server.

    Therefore:

    DNS != SERVER_IP

    does not automatically mean:

    wrong

    for every architecture.


    58. Add DNS Mode

    Eventually site metadata can contain:

    DNS_MODE=DIRECT

    or:

    DNS_MODE=PROXY

    or:

    DNS_MODE=EXTERNAL

    For the current CHP server:

    DNS_MODE=DIRECT

    can be the default.


    59. Direct Mode

    example.com
         ↓
    server IP

    The DNS checker expects:

    A → CHP server

    60. External/Proxy Mode

    example.com
         ↓
    CDN
         ↓
    CHP server

    The DNS checker should instead verify the appropriate expected configuration.

    This can be added later.


    61. Don’t Overcomplicate Version 1

    For the current platform, implement:

    DIRECT DNS

    with:

    A
    AAAA
    CNAME
    NS
    TTL

    Then add provider/CDN support later.


    62. DNS Check Output — Full Example

    CresignSys DNS Check
    ====================
    
    Domain:
    example.com
    
    Nameservers:
    ns1.provider.com
    ns2.provider.com
    
    A Records:
    203.0.113.10
    
    Expected IPv4:
    203.0.113.10
    
    AAAA Records:
    None
    
    CNAME:
    None
    
    TTL:
    300
    
    DNS State:
    CORRECT

    63. Pending Example

    CresignSys DNS Check
    ====================
    
    Domain:
    example.com
    
    A Records:
    None
    
    AAAA Records:
    None
    
    DNS State:
    PENDING
    
    Required:
    A @ → YOUR_SERVER_IP

    64. Wrong Example

    CresignSys DNS Check
    ====================
    
    Domain:
    example.com
    
    A Records:
    198.51.100.20
    
    Expected:
    203.0.113.10
    
    DNS State:
    WRONG

    65. Multiple Example

    CresignSys DNS Check
    ====================
    
    Domain:
    example.com
    
    A Records:
    203.0.113.10
    198.51.100.20
    
    Expected:
    203.0.113.10
    
    DNS State:
    MULTIPLE

    66. DNS Error Example

    CresignSys DNS Check
    ====================
    
    Domain:
    example.com
    
    DNS resolver:
    ERROR
    
    DNS State:
    ERROR
    
    No changes made.

    67. Integration With hosting-health

    Now:

    sudo hosting-health example.com

    can start with:

    [OK] DNS

    and then:

    [OK] Nginx
    [OK] PHP-FPM
    [OK] PHP socket
    [OK] Database
    [OK] WordPress
    [OK] HTTPS

    68. Integration With hosting-ssl

    The SSL workflow becomes:

    hosting-ssl
         │
         ▼
    hosting-dns-check
         │
     ┌───┴────┐
     ▼        ▼
    CORRECT   FAIL
     │         │
     ▼         ▼
    ACME      STOP

    This is much safer.


    69. Integration With Domain Management

    Now:

    hosting-domain-add
            ↓
    DNS=PENDING
            ↓
    hosting-dns-check
            ↓
    DNS=CORRECT
            ↓
    hosting-ssl
            ↓
    SSL=ACTIVE

    This gives the platform a clean provisioning pipeline.


    70. Lesson 075 — Core Principle

    DNS is the bridge between:

    DOMAIN

    and:

    SERVER

    Therefore the hosting platform should never simply assume:

    domain added = DNS ready

    Instead:

    DOMAIN
       ↓
    DNS CHECK
       ↓
    CORRECT
       ↓
    HTTP
       ↓
    SSL
       ↓
    HTTPS

    71. Updated CHP Architecture

                             CHP
                              │
              ┌───────────────┼────────────────┐
              ▼               ▼                ▼
            SITE            DOMAIN             DNS
              │               │                │
              │               ├── Primary      │
              │               ├── Alias        │
              │               └── Redirect     │
              │                                │
              │                                ▼
              │                           DNS CHECK
              │                                │
              └────────────────────────────────┘
                               │
                               ▼
                             NGINX
                               │
                               ▼
                              SSL
                               │
                               ▼
                           PHP-FPM
                               │
                               ▼
                           WordPress

    72. Current Command Set

    hosting-create DOMAIN
    
    hosting-list
    hosting-info DOMAIN
    hosting-health DOMAIN
    
    hosting-repair DOMAIN --php
    hosting-repair DOMAIN --nginx
    hosting-repair DOMAIN --permissions
    hosting-repair DOMAIN --wordpress
    
    hosting-ssl DOMAIN
    
    hosting-backup DOMAIN
    hosting-backup-list DOMAIN
    hosting-backup-verify DOMAIN BACKUP_ID
    hosting-backup-prune DOMAIN --dry-run
    hosting-backup-prune DOMAIN --execute
    hosting-restore DOMAIN BACKUP_ID
    
    hosting-suspend DOMAIN
    hosting-unsuspend DOMAIN
    
    hosting-domain-add DOMAIN
    hosting-domain-add DOMAIN --alias TARGET
    hosting-domain-add DOMAIN --redirect TARGET
    hosting-domain-list DOMAIN
    hosting-domain-remove DOMAIN
    
    hosting-dns-check DOMAIN

    Next Lesson — 076

    Build hosting-site-status

    We now have enough components to build a single unified status engine.

    Instead of checking:

    hosting-dns-check example.com
    hosting-health example.com
    hosting-info example.com

    separately, we can create:

    sudo hosting-site-status example.com

    that produces one complete diagnostic:

    DOMAIN
     ├── Ownership
     ├── DNS
     ├── Nginx
     ├── HTTP
     ├── HTTPS
     ├── SSL
     ├── PHP-FPM
     ├── Database
     ├── WordPress
     ├── Filesystem
     ├── Backup
     ├── Service State
     └── Overall Health

    The result will allow CHP to classify a site as:

    HEALTHY
    DEGRADED
    SUSPENDED
    WAITING_DNS
    SSL_ERROR
    APPLICATION_ERROR
    DATABASE_ERROR
    SERVER_ERROR

    That unified status layer will become the foundation for the future CresignSys Hosting control panel.