CresignSys Learn — Lesson 066

Written by

in

Make hosting-create Production-Safe

Lesson 065 created the first working provisioning engine.

Now we improve it so that a failed operation does not leave the server in an inconsistent state.

The new philosophy is:

REQUEST
   ↓
VALIDATE EVERYTHING POSSIBLE
   ↓
DRY RUN / PLAN
   ↓
CREATE RESOURCES
   ↓
VALIDATE EACH RESOURCE
   ↓
ACTIVATE
   ↓
HEALTH CHECK
   ↓
ACTIVE

If something fails:

PROVISIONING
      ↓
     ERROR
      ↓
RECOVERY / ROLLBACK

1. Why Production Safety Matters

Imagine:

Linux user       ✓
directories      ✓
database         ✓
DB user          ✓
PHP-FPM          ✓
Nginx            ✗
WordPress        not reached

You now have a partially created website.

If you simply run:

hosting-create example.com

again, the script might say:

ERROR: user already exists

and stop.

This is why provisioning needs a defined state model.


2. Provisioning States

Use:

NEW
  ↓
VALIDATING
  ↓
PROVISIONING
  ↓
TESTING
  ↓
ACTIVE

Failure:

PROVISIONING
      ↓
     ERROR

Recovery:

ERROR
 ↓
REPAIR
 ↓
TESTING
 ↓
ACTIVE

3. Never Automatically Delete Customer Data

A dangerous rollback design is:

failure
 ↓
rm -rf website

This is unacceptable once real customer data exists.

Instead, distinguish:

Newly created resource

Can potentially be rolled back.

Existing customer resource

Must not be destroyed automatically.

This distinction is critical.


4. Add a State File

Create:

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

with:

state
site.conf
provision.log

For example:

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

5. State File

Example:

STATUS=PROVISIONING
DOMAIN=example.com
SITE_ID=example_com

When everything succeeds:

STATUS=ACTIVE

If something fails:

STATUS=ERROR

6. Why State Is Important

Your control panel can later ask:

What happened to example.com?

and get:

STATUS=ERROR

instead of guessing from:

directory exists?
user exists?
database exists?

7. Create a State Function

Conceptually:

set_state() {
    local state="$1"

    printf 'STATUS=%s\n' "$state" > \
        "${SITE_STATE_DIR}/state"
}

Then:

set_state "PROVISIONING"

or:

set_state "ACTIVE"

8. Add Provisioning ID

Every provisioning attempt should have an identifier.

For example:

PROV-20260813-153501-12345

This is useful when several operations occur simultaneously.

Generate something unique, such as:

PROVISION_ID="$(date +%Y%m%d-%H%M%S)-$$"

Then log:

PROVISION_ID=20260813-153501-12345

9. Per-Site Log

Instead of putting everything only in:

/var/log/cresignsys/hosting-create.log

also create:

/var/lib/cresignsys/sites/example.com/provision.log

Now troubleshooting a single website is much easier.


10. Better Logging

Use:

INFO
WARNING
ERROR
SUCCESS

Example:

[INFO] Creating Linux user
[INFO] Creating database
[SUCCESS] Database created
[INFO] Creating PHP-FPM pool
[ERROR] PHP-FPM validation failed

11. Centralized Error Trap

Bash can trap unexpected failures.

Conceptually:

trap 'handle_error $LINENO' ERR

Then:

handle_error() {
    set_state "ERROR"
    log "Provisioning failed at line $1"
}

This gives you a safety net for unexpected errors.


12. But Don’t Rely Only on trap

A trap tells you:

something failed

It doesn’t necessarily tell you:

what should be safely removed

Therefore explicit stage tracking is better.


13. Track Completed Stages

For example:

USER_CREATED=1
DIRECTORIES_CREATED=1
DATABASE_CREATED=1
DB_USER_CREATED=1
PHP_POOL_CREATED=1
NGINX_CREATED=1
WORDPRESS_CREATED=1

Initially:

USER_CREATED=0

After success:

USER_CREATED=1

14. Why Track Stages?

Suppose:

USER_CREATED=1
DIRECTORIES_CREATED=1
DATABASE_CREATED=1
DB_USER_CREATED=1
PHP_POOL_CREATED=0

You know exactly where provisioning stopped.


15. Rollback Logic

A simplified development model:

if PHP pool creation fails:
    remove newly-created DB user
    remove newly-created DB
    remove newly-created directories
    remove newly-created Linux user

But only if those resources were created by this provisioning attempt.


16. Never Roll Back Blindly

Bad:

mysql -e "DROP DATABASE example_db"

without first proving:

this database
was created by this provisioning transaction

Production systems must protect against accidental deletion.


17. Resource Ownership Record

Record:

RESOURCE
CREATED_BY_THIS_RUN

For example:

USER_CREATED=1
DB_CREATED=1
DB_USER_CREATED=1
PHP_POOL_CREATED=1
NGINX_CREATED=1

Then rollback can act only on those resources.


18. --dry-run

This is one of the most useful additions.

Run:

sudo hosting-create example.com --dry-run

It should not modify the server.

Instead:

CresignSys Provisioning Plan
----------------------------

Domain:
example.com

Linux user:
example_com

Website:
 /storage/websites/example.com/public

Database:
example_com_db

PHP socket:
 /run/php/example_com.sock

Nginx:
 /etc/nginx/sites-available/example.com.conf

Actions:
 [ ] Create user
 [ ] Create directories
 [ ] Create database
 [ ] Create DB user
 [ ] Create PHP-FPM pool
 [ ] Create Nginx configuration
 [ ] Install WordPress
 [ ] Health check

19. Why Dry Run?

It allows you to catch:

wrong domain
wrong PHP version
wrong paths
wrong database names
existing resources

before modifying anything.


20. Argument Parsing

Instead of accepting only:

hosting-create example.com

support:

hosting-create example.com --dry-run

and perhaps:

hosting-create example.com --php=8.3

later.

For now, keep the options limited.


21. Don’t Add --force Casually

A dangerous option is:

hosting-create example.com --force

because users may assume it means:

repair

while the implementation might accidentally mean:

overwrite everything

Instead, define explicit operations:

hosting-repair
hosting-reprovision
hosting-delete

rather than a vague --force.


22. Duplicate Detection

Before provisioning, check:

domain exists?
site state exists?
Linux user exists?
database exists?
PHP pool exists?
Nginx config exists?

If any conflict exists:

ERROR: Existing resource detected.

Do not continue.


23. Example

Suppose:

/storage/websites/example.com

already exists.

Output:

ERROR: Website directory already exists.
Domain: example.com
Action: use hosting-info or hosting-repair.

This is safer than overwriting it.


24. Better Domain Validation

A domain consists of labels:

example.com

or:

learn.cresignsys.com

Each label should be handled safely.

Your validation should reject obvious filesystem/control characters such as:

/
\
:
;
'
"
$
`

and whitespace.


25. Domain vs URL

Don’t accept:

https://example.com

when the command expects:

example.com

Similarly:

example.com/path

should be rejected.

The provisioning command should receive the hostname, not a URL.


26. Validate Maximum Length

A production domain validator should enforce appropriate DNS length constraints.

At minimum, your system should not permit arbitrarily long values to become:

Linux usernames
database identifiers
file names
Nginx configuration names

27. Separate Display Name and Technical ID

This is an important architectural improvement.

Customer-facing:

Domain:
my-business.example

Internal:

Site ID:
site_000127

Then resources become:

Linux user:
csp127

PHP pool:
csp127

Database:
csp127_wp

instead of deriving everything directly from the domain.


28. Why Site IDs Are Better

Consider:

very-long-business-name-with-many-words.example.com

A domain-derived Linux/database identifier can become cumbersome.

Instead:

site_id = 127

and:

site_127

is much easier internally.


29. Recommended CHP Model

Use:

Domain
    ↓
Site ID
    ↓
Resource names

Example:

DOMAIN
learn.cresignsys.com

SITE ID
1027

LINUX USER
csp1027

DATABASE
csp1027_wp

DB USER
csp1027_db

PHP POOL
csp1027

30. This Also Prevents Naming Collisions

Two domains may produce awkward normalized names.

A unique site ID guarantees:

site_1027
site_1028
site_1029

are unique.

The domain remains the public identifier.


31. Site Registry

Your control database should eventually contain:

sites
-----
id
domain
status
linux_user
database_name
database_user
php_version
document_root
created_at

Example:

1027
learn.cresignsys.com
ACTIVE
csp1027
csp1027_wp
csp1027_db
8.3
/storage/websites/learn.cresignsys.com/public

32. This Changes hosting-create

Instead of:

hosting-create
 ↓
generate everything from domain

the future architecture becomes:

hosting-create
 ↓
reserve Site ID
 ↓
create resources from Site ID
 ↓
store metadata

This is much stronger.


33. Concurrency Problem

Imagine two administrators run:

hosting-create example.com

at exactly the same time.

Both processes might check:

Does site exist?
NO

Then both attempt to create it.

This is called a:

Race Condition


34. Use a Lock

The script should acquire a global or per-domain lock.

For example:

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

Then:

Process A
   ↓
gets lock
   ↓
provisions

Process B
   ↓
cannot get lock
   ↓
waits/fails

35. Why Locks Matter

Without locking:

Process A → create DB
Process B → create DB
Process A → create user
Process B → create user

and you can get unpredictable failures.


36. flock

Linux provides:

flock

which is useful for this.

Conceptually:

flock -n "$LOCK_FILE" ...

This gives your provisioning operation a simple concurrency-control mechanism.


37. Disk Space Validation

Before creating:

df -P "$WEB_ROOT_BASE"

Check available space.

If insufficient:

ERROR:
Insufficient disk space.

Stop before creating resources.


38. Inode Check

Disk space isn’t the only issue.

Check:

df -Pi "$WEB_ROOT_BASE"

A filesystem can have free gigabytes but run out of inodes.

This matters on servers hosting many small files.


39. Service Health

Before provisioning:

Nginx ✓
MySQL ✓
PHP-FPM ✓
Storage ✓
WP-CLI ✓

If one is unavailable:

don't start provisioning

This avoids unnecessary partial deployments.


40. PHP Version Validation

Check:

php-fpm8.3 -t

and:

systemctl is-active php8.3-fpm

If your configured PHP version doesn’t exist:

ERROR:
Configured PHP version 8.3 is unavailable.

41. WordPress Compatibility

Your platform should eventually define supported PHP versions centrally.

For example:

PHP profile:
wordpress-default

rather than letting arbitrary users request unsupported versions.


42. Secret Handling Improvement

The first version generated:

DB_PASSWORD

inside the shell.

Now consider where it goes.

It must eventually be stored in:

wp-config.php

but should not be:

logged
printed
stored in shell history

43. Don’t Log Secrets

Never do:

log "DB_PASSWORD=$DB_PASSWORD"

Never.

Logs should contain:

database created

not:

database password = ...

44. WordPress Admin Password

The same rule applies.

Do not print:

ADMIN_PASSWORD

to:

terminal
log file
control panel logs

unless the product deliberately provides a secure one-time credential mechanism.


45. Better Future Design

Eventually:

CHP
 ↓
credential generator
 ↓
secure credential storage
 ↓
one-time display / password reset

rather than:

Bash output
 ↓
password

46. File Permission for wp-config.php

Because wp-config.php contains database credentials, its permissions should be deliberately controlled.

After installation, inspect:

ls -l wp-config.php

The exact mode should be chosen based on your PHP-FPM/Nginx ownership architecture.

The goal is:

PHP can read it
unnecessary users cannot modify it

47. Don’t Over-Harden Blindly

You may see recommendations like:

chmod 400 wp-config.php

But if PHP or your management process cannot access it, WordPress breaks.

Security must be balanced with actual process identities.


48. Nginx Security Rules

Your Nginx template should also prevent access to hidden files.

For example:

location ~ /\. {
    deny all;
}

This helps block requests to files such as:

.git/
.env
.htaccess

depending on the exact Nginx configuration.


49. Protect Sensitive Files

You should also think about:

backup.zip
database.sql
.env
.git
composer.json
composer.lock

Some files should never be publicly downloadable.

A hardened template should explicitly define what should and should not be exposed.


50. PHP File Upload Limits

WordPress sites may need settings such as:

upload_max_filesize
post_max_size
memory_limit
max_execution_time

Don’t hard-code huge values for every site.

These should become:

hosting plan settings

later.


51. Example Plan Configuration

Starter
memory_limit = 256M
upload_max_filesize = 64M

Business
memory_limit = 512M
upload_max_filesize = 128M

Professional
memory_limit = 768M
upload_max_filesize = 256M

These are example policy values, not universal recommendations.


52. Resource Profiles

Instead of putting resource settings directly into hosting-create, create:

/etc/cresignsys/plans/

For example:

starter.conf
business.conf
professional.conf

Then:

site
 ↓
plan
 ↓
resource configuration

53. This Is the Beginning of Resource Governance

Your platform eventually controls:

Storage
CPU-related PHP concurrency
Memory-related PHP settings
Database limits
Backup retention
Bandwidth policy

This is what transforms a server into a hosting service.


54. Add hosting-info

A useful next command:

sudo hosting-info example.com

Output:

Domain:
example.com

Status:
ACTIVE

Site ID:
1027

Linux user:
csp1027

Web root:
/storage/websites/example.com/public

PHP:
8.3

Database:
csp1027_wp

SSL:
ACTIVE

This should become a read-only diagnostic command.


55. Add hosting-list

Eventually:

sudo hosting-list

could show:

DOMAIN                     STATUS
------------------------------------------------
learn.cresignsys.com       ACTIVE
shop.cresignsys.com        ACTIVE
example.com                ERROR

This will become the CLI equivalent of the control panel’s website list.


56. Add hosting-health

For one site:

sudo hosting-health example.com

Output:

Linux user       ✓
Website files    ✓
PHP-FPM          ✓
PHP socket       ✓
Nginx            ✓
Database         ✓
WordPress        ✓
HTTPS            ✓

57. Repair Instead of Recreate

If:

PHP socket ✗

you should eventually be able to run:

sudo hosting-repair example.com --php

If:

Nginx ✗

then:

sudo hosting-repair example.com --nginx

This is much safer than recreating the entire site.


58. Provisioning Becomes Modular

Eventually:

hosting-create
       │
       ├── validate
       ├── filesystem
       ├── database
       ├── php
       ├── nginx
       ├── wordpress
       └── health

and:

hosting-repair
       │
       ├── filesystem
       ├── database
       ├── php
       ├── nginx
       └── health

can reuse the same modules.


59. Recommended File Layout

A more mature CHP server might look like:

/etc/cresignsys/
├── hosting.conf
├── plans/
│   ├── starter.conf
│   ├── business.conf
│   └── professional.conf
├── templates/
│   ├── nginx-wordpress.conf
│   └── php-fpm.conf
└── scripts/
    ├── common.sh
    ├── filesystem.sh
    ├── database.sh
    ├── php.sh
    ├── nginx.sh
    ├── wordpress.sh
    └── health.sh

60. The Main Command Becomes Small

Instead of one enormous Bash file:

hosting-create

the main script eventually becomes an orchestrator:

validate
 ↓
filesystem.create
 ↓
database.create
 ↓
php.create
 ↓
nginx.create
 ↓
wordpress.create
 ↓
health.check

This is a much better long-term architecture.


61. Production-Safe Provisioning Flow

The improved workflow is:

              hosting-create
                     │
                     ▼
              Validate request
                     │
                     ▼
              Acquire lock
                     │
                     ▼
               Dry-run plan
                     │
                     ▼
             Create state record
                     │
                     ▼
              Create filesystem
                     │
                     ▼
              Create database
                     │
                     ▼
              Create PHP-FPM
                     │
                     ▼
               Create Nginx
                     │
                     ▼
               Install WP
                     │
                     ▼
               Health checks
                     │
                     ▼
                   ACTIVE

Failure anywhere:

                    ERROR
                      │
             ┌────────┴────────┐
             ▼                 ▼
          recover            rollback

with rollback restricted to resources created by that operation.


62. What We Should Not Automate Yet

Don’t add everything at once.

Avoid immediately combining:

DNS API
SSL
backups
billing
email
resource quotas
firewall changes

into one Bash script.

First make:

hosting-create

reliable.

Then add each subsystem separately.


63. Lesson 066 — Core Principle

The difference between a script and a hosting platform is reliability.

A simple script says:

run commands

A provisioning engine says:

validate
lock
plan
create
verify
record
recover
activate

That distinction is fundamental.


64. Target CHP Command Set

The architecture we are building toward is:

hosting-create DOMAIN
hosting-list
hosting-info DOMAIN
hosting-health DOMAIN
hosting-repair DOMAIN
hosting-ssl DOMAIN
hosting-backup DOMAIN
hosting-restore DOMAIN
hosting-suspend DOMAIN
hosting-unsuspend DOMAIN
hosting-delete DOMAIN

Each command should perform one clearly defined operational task.


Next Lesson — 067

Build hosting-info, hosting-list and hosting-health

Before adding SSL, we should build the observability layer.

You will learn to create:

sudo hosting-list
sudo hosting-info example.com
sudo hosting-health example.com

These commands will inspect:

Domain
Site ID
Status
Linux user
Directory
Disk usage
PHP-FPM
PHP socket
Nginx
Database
WordPress
SSL

This gives your CresignSys Hosting Platform the ability to see and diagnose every hosted website before we add more automation.

Comments

Leave a Reply

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