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.
Leave a Reply