Build hosting-backup-list and Backup Retention
We now have:
hosting-create
hosting-list
hosting-info
hosting-health
hosting-repair
hosting-ssl
hosting-backup
hosting-restore
The backup system now needs management.
Without retention, this happens:
Backup
↓
Backup
↓
Backup
↓
Backup
↓
Backup
↓
DISK FULL
So this lesson builds:
sudo hosting-backup-list example.com
and:
sudo hosting-backup-prune example.com
1. Backup Management Architecture
BACKUPS
│
┌────────────┼────────────┐
▼ ▼ ▼
LIST VERIFY PRUNE
│ │ │
▼ ▼ ▼
Inventory Integrity Retention
The commands have separate responsibilities.
2. hosting-backup-list
Purpose:
Show available backups without modifying anything.
Example:
CresignSys Backups
==================
Domain: example.com
BACKUP ID STATUS SIZE AGE
--------------------------------------------------------
2026-08-13_160000 COMPLETE 1.42 GB 2 hours
2026-08-12_160000 COMPLETE 1.38 GB 1 day
2026-08-11_160000 COMPLETE 1.35 GB 2 days
2026-08-10_160000 COMPLETE 1.31 GB 3 days
3. Backup Directory
Our existing structure:
/backup/cresignsys/
└── example.com/
├── 2026-08-13_160000/
├── 2026-08-12_160000/
└── 2026-08-11_160000/
Each directory represents one backup.
4. Backup Manifest
Every backup should contain:
manifest.json
files.tar.gz
database.sql.gz
checksums.sha256
site.conf
Example:
2026-08-13_160000/
├── files.tar.gz
├── database.sql.gz
├── checksums.sha256
├── site.conf
└── manifest.json
The manifest is what makes the backup self-describing.
5. Create hosting-backup-list
Create:
sudo nano /usr/local/bin/hosting-backup-list
Start:
#!/usr/bin/env bash
set -Eeuo pipefail
BACKUP_BASE="/backup/cresignsys"
6. Validate Domain
if [[ $# -ne 1 ]]; then
echo "Usage: hosting-backup-list DOMAIN"
exit 1
fi
DOMAIN="$1"
SITE_BACKUP_DIR="${BACKUP_BASE}/${DOMAIN}"
Then:
if [[ ! -d "$SITE_BACKUP_DIR" ]]; then
echo "No backups found for $DOMAIN"
exit 0
fi
7. List Backup Directories
for backup_dir in "$SITE_BACKUP_DIR"/*; do
[[ -d "$backup_dir" ]] || continue
BACKUP_ID="$(basename "$backup_dir")"
echo "$BACKUP_ID"
done
Initially this is enough to prove the structure works.
8. Read Manifest Status
For each backup:
manifest.json
read:
status
created_at
For example:
{
"status": "COMPLETE",
"created_at": "2026-08-13T16:00:00"
}
Then output:
2026-08-13_160000 COMPLETE
9. Calculate Backup Size
Use:
du -sh "$backup_dir"
Example:
2026-08-13_160000 COMPLETE 1.42G
10. Sort Backups
Backup IDs use:
YYYY-MM-DD_HHMMSS
which has the useful property that lexical sorting corresponds to chronological sorting.
Therefore:
2026-08-11...
2026-08-12...
2026-08-13...
sort naturally.
11. Newest First
The administrator usually wants the newest backup first:
2026-08-13
2026-08-12
2026-08-11
Use reverse sorting.
12. Better Output
The final command should eventually produce:
CresignSys Backup Inventory
============================
Domain: example.com
BACKUP ID STATUS SIZE
------------------------------------------------
2026-08-13_160000 COMPLETE 1.42G
2026-08-12_160000 COMPLETE 1.38G
2026-08-11_160000 COMPLETE 1.35G
2026-08-10_160000 COMPLETE 1.31G
Total backups: 4
Total storage: 5.46G
13. Failed Backups
Suppose:
2026-08-13_160000
failed during database backup.
The inventory should show:
2026-08-13_160000 ERROR 1.40G
Do not treat it as a valid restore point.
14. Why Failed Backups Matter
A failed backup can consume substantial storage.
For example:
files backup ✓
database backup ✗
might still leave:
1.4 GB
on disk.
These should eventually be cleaned up.
15. Backup Integrity Command
Eventually add:
sudo hosting-backup-verify example.com BACKUP_ID
Example:
sudo hosting-backup-verify example.com 2026-08-13_160000
Output:
[OK] Manifest
[OK] Files archive
[OK] Database archive
[OK] Checksums
Backup: VALID
This should remain separate from backup-list.
16. Why Separate Verification?
Listing should be fast.
Verification may involve:
large archive
↓
checksum calculation
↓
CPU + disk I/O
You don’t want:
hosting-backup-list
to recalculate every backup’s checksum every time.
17. Retention
Now the important part.
Suppose we create:
1 backup/day
for a year:
365 backups
If each is:
1 GB
we have:
365 GB
for one website.
At scale this becomes expensive.
18. Retention Policy
A retention policy answers:
Which backups should remain, and which may be deleted?
Example:
Daily:
7
Weekly:
4
Monthly:
3
So you might retain:
7 recent daily backups
4 weekly backups
3 monthly backups
19. Retention Is Not Simply “Delete Older Than X”
A naive policy:
delete backups older than 30 days
is easy.
But:
30 days
might not give you useful historical coverage.
A better policy preserves representative restore points.
20. Example
Suppose:
Daily backups:
every day
Weekly backups:
every Sunday
Monthly backups:
first day of month
You might keep:
Recent 7 days
+
4 weekly points
+
3 monthly points
This provides both recent and historical recovery.
21. Retention Tags
The backup manifest can eventually include:
{
"backup_id": "2026-08-13_160000",
"retention": "DAILY"
}
or:
{
"retention": "WEEKLY"
}
or:
{
"retention": "MONTHLY"
}
22. Better: Don’t Hard-Code Tags at Creation
The retention engine can calculate the category from the timestamp.
For example:
today
↓
daily
Sunday
↓
weekly
first day of month
↓
monthly
This allows retention policy changes without rewriting old backups.
23. Retention Configuration
Add to:
/etc/cresignsys/hosting.conf
for example:
BACKUP_DAILY_RETENTION=7
BACKUP_WEEKLY_RETENTION=4
BACKUP_MONTHLY_RETENTION=3
These are example defaults.
24. Per-Plan Retention
Eventually:
Starter
daily = 3
weekly = 2
monthly = 1
Business
daily = 7
weekly = 4
monthly = 2
Professional
daily = 14
weekly = 8
monthly = 6
This lets backup service become part of the hosting plan.
25. Never Delete the Only Backup
The prune engine must protect against:
Total valid backups = 1
It should never delete the final usable backup simply because it is old.
Minimum safety rule:
Keep at least 1 valid backup.
26. Never Delete the Newest Valid Backup
Another important rule:
newest valid backup
should always be protected.
Even if retention calculation says it is old.
27. Failed Backup Cleanup
Failed backups can be handled separately.
Example policy:
ERROR backups:
delete after 2 days
because they aren’t valid recovery points.
But retain enough logs/metadata to diagnose failures.
28. hosting-backup-prune
Create:
sudo nano /usr/local/bin/hosting-backup-prune
Start:
#!/usr/bin/env bash
set -Eeuo pipefail
Arguments:
hosting-backup-prune DOMAIN
and later:
hosting-backup-prune DOMAIN --dry-run
29. Dry Run Is Essential
Never make deletion the default while developing.
Run:
sudo hosting-backup-prune example.com --dry-run
Output:
CresignSys Backup Prune
=======================
Domain: example.com
KEEP:
2026-08-13_160000
2026-08-12_160000
2026-08-11_160000
DELETE:
2026-07-01_160000
2026-06-24_160000
No files deleted.
30. Why Dry Run Matters
A retention bug can become:
backup-prune
↓
wrong date calculation
↓
DELETE ALL
A dry-run stage allows you to inspect the selection first.
31. Explicit Confirmation
For actual deletion:
sudo hosting-backup-prune example.com --execute
This makes the destructive nature obvious.
Avoid vague:
--force
32. Selection Algorithm
Conceptually:
1. Find all backups
2. Read status
3. Ignore invalid backups for restore retention
4. Sort chronologically
5. Protect newest valid backup
6. Identify daily backups to keep
7. Identify weekly backups to keep
8. Identify monthly backups to keep
9. Mark remaining backups as deletable
10. Show plan
11. Delete only after explicit execution
33. Backup Selection Example
Suppose you have:
Aug 13
Aug 12
Aug 11
Aug 10
Aug 09
Aug 08
Aug 07
Aug 06
Aug 05
Aug 04
...
Daily retention:
7
means:
Aug 13
Aug 12
Aug 11
Aug 10
Aug 09
Aug 08
Aug 07
are protected as recent daily points.
34. Weekly Protection
Suppose Sundays are:
Aug 09
Aug 02
Jul 26
Jul 19
If weekly retention is:
4
all four remain even if they’re older than the daily window.
35. Monthly Protection
Suppose:
Aug 01
Jul 01
Jun 01
and monthly retention is:
3
those remain.
36. Result
The system retains:
recent daily
+
historical weekly
+
historical monthly
instead of simply:
latest N backups
37. Don’t Delete a Backup That Is Being Restored
The restore operation must acquire the same site lock.
Therefore:
restore
↓
LOCK
and:
prune
↓
LOCK
Only one operation can modify backup state for that site at a time.
38. Better: Global Backup Lock
There may also be a global backup storage lock:
/var/lock/cresignsys-backup.lock
This prevents two processes from simultaneously manipulating the same backup repository metadata.
For the initial implementation, a per-site lock plus safe filesystem operations may be sufficient.
39. Backup Storage Limit
Retention alone isn’t enough.
Suppose:
Backup storage limit = 500 GB
and usage reaches:
490 GB
The system should report:
WARNING:
Backup storage 98% full.
40. Backup Monitoring
Add:
Backup Storage
--------------
Used: 490 GB
Available: 10 GB
Usage: 98%
This should become part of the server dashboard.
41. What Happens When Storage Is Full?
The worst design:
backup
↓
disk full
↓
partial backup
↓
database dump fails
Better:
check available storage
↓
enough?
┌────┴────┐
▼ ▼
YES NO
│ │
backup alert
42. Preflight Storage Check
Before backup:
df -P "$BACKUP_BASE"
Calculate available bytes.
For a large site, estimate required space.
A simple safety margin can be used.
For example:
required:
current site size
+
database size
+
safety margin
43. Don’t Promise Exact Backup Size
Compression changes the final size.
A website containing:
JPEG
PNG
MP4
ZIP
PDF
may already contain compressed data.
Therefore:
uncompressed size
does not directly predict:
compressed backup size
Use estimates only as safety checks.
44. Backup Storage Directory Permissions
Protect:
/backup/cresignsys/
from normal web users.
The directory should not be writable by:
www-data
unless there is a deliberate architecture requiring it.
Ideally:
root
↓
backup engine
↓
backup repository
45. Don’t Let WordPress Write Backups
Avoid:
WordPress
↓
/backup/cresignsys/
The web application should not have unrestricted access to all customer backups.
This creates unnecessary security risk.
46. Backup Ownership
A sensible architecture is:
backup repository
owner: root
group: dedicated backup group
with carefully controlled access.
Exact permissions should follow your operational requirements.
47. Backup Encryption
If backups remain on the same server, encryption may still be useful.
Especially because backups contain:
database
customer files
credentials
For off-site storage, encryption should be considered mandatory for sensitive customer data.
48. Remote Backup Retention
Eventually:
Local retention
+
Remote retention
can be separate.
For example:
Local:
7 daily
Remote:
30 daily
because local storage is cheaper/faster while remote storage provides disaster recovery.
49. Remote Backup Architecture
CHP
│
▼
Backup
│
┌──────┴──────┐
▼ ▼
Local Remote
│ │
▼ ▼
Fast restore Disaster recovery
50. Remote Storage Should Not Be Mounted Publicly
Avoid exposing:
remote backup storage
through:
Nginx
FTP
public directory
Use an appropriate storage API/protocol with least-privilege credentials.
51. Backup Encryption Key
Do not store:
backup encryption key
inside:
files.tar.gz
or:
manifest.json
The encryption key needs separate protection.
52. Backup Deletion Audit
Every deletion should be logged:
2026-08-13 18:00
DELETE BACKUP
Domain: example.com
Backup: 2026-06-01_160000
Reason: RETENTION
Operator: root
This is important for accountability.
53. Don’t Permanently Delete Immediately
For a more mature system, consider:
eligible
↓
scheduled deletion
↓
deleted
This creates a grace period.
But this consumes additional storage, so it must be balanced against capacity.
54. Backup Inventory Database
Eventually you don’t want to scan:
/backup/cresignsys/
every time.
Create metadata:
backups
-------
id
site_id
backup_id
created_at
status
files_size
database_size
storage_location
retention_class
verified
Then:
hosting-backup-list
can query metadata quickly.
55. Why This Matters at Scale
With:
10 sites
filesystem scanning is easy.
With:
1,000 sites
×
365 backups
you have:
365,000 backup directories
A database-backed inventory becomes much more practical.
56. Backup Database Model
Eventually:
sites
│
│ 1:N
▼
backups
Example:
sites
-----
id
domain
backups
-------
id
site_id
backup_id
status
created_at
size
location
57. Backup Storage Locations
Don’t assume every backup lives locally.
Use:
storage_location
Examples:
LOCAL
REMOTE_S3
REMOTE_OBJECT
Then:
hosting-backup-list
can show:
BACKUP LOCATION
----------------------------------
2026-08-13_160000 LOCAL
2026-08-12_160000 REMOTE
58. Backup Health
A backup itself can have health:
VALID
INVALID
VERIFYING
ERROR
So you eventually have:
SITE HEALTH
+
BACKUP HEALTH
59. Dashboard
The future CHP dashboard could show:
Backup Overview
================================
Sites: 42
Backup Healthy: 40
Backup Warning: 1
Backup Failed: 1
Storage:
Local: 380 GB
Remote: 1.2 TB
Oldest valid backup:
2026-05-01
Failed backups:
3
60. Backup Policy Per Site
site.conf can eventually contain:
BACKUP_ENABLED=true
BACKUP_DAILY=7
BACKUP_WEEKLY=4
BACKUP_MONTHLY=3
Then each site can have a different policy.
61. Don’t Let Customers Arbitrarily Set Huge Retention
If the hosting plan allows:
7 daily
a customer shouldn’t be able to request:
10,000 daily backups
without the platform accounting for the resulting storage.
This is another example of:
plan
↓
resource policy
62. Backup Quota
Eventually:
Backup Quota:
20 GB
and:
Current:
17.5 GB
The system can show:
87.5% used
63. Backup Quota vs Website Storage
These are different resources.
Website Storage
+
Backup Storage
For example:
Website:
5 GB
Backup:
20 GB
A site using 4 GB doesn’t necessarily mean it has only 1 GB of total backup capacity.
64. Lesson 072 — Core Principle
Backup management has three separate jobs:
Inventory
What backups exist?
Integrity
Which backups are valid?
Retention
Which backups should remain?
Therefore:
hosting-backup-list
hosting-backup-verify
hosting-backup-prune
should remain separate operations.
65. Current Backup Architecture
BACKUP SYSTEM
│
┌────────────┼────────────┐
▼ ▼ ▼
CREATE LIST VERIFY
│ │ │
└────────────┼────────────┘
▼
RETAIN
│
▼
PRUNE
│
▼
RESTORE
This is now a complete basic backup lifecycle.
66. Current CHP 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
Next Lesson — 073
Build hosting-suspend and hosting-unsuspend
The next requirement is account/site lifecycle control.
We need to be able to temporarily disable a website without deleting it:
sudo hosting-suspend example.com
and later restore access:
sudo hosting-unsuspend example.com
The architecture will become:
ACTIVE
│
▼
SUSPENDED
│
├── website access blocked
├── database preserved
├── files preserved
├── SSL preserved
└── backups preserved
│
▼
UNSUSPEND
│
▼
HEALTH CHECK
│
▼
ACTIVE
This will also introduce an important hosting-platform concept:
suspension must be reversible and must never mean deletion.
Leave a Reply