CresignSys Learn — Lesson 072

Written by

in

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.

Comments

Leave a Reply

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