Build the CHP Backup Scheduler & Retention Engine
We now have:
hosting-backup
hosting-backup-list
hosting-backup-verify
hosting-restore
The next step is to make backups automatic.
The architecture becomes:
BACKUP POLICY
│
▼
SCHEDULER
│
▼
BACKUP JOB
│
┌───────────┴───────────┐
▼ ▼
SUCCESS FAIL
│ │
▼ ▼
RETENTION ALERT
│
▼
SAFE PRUNING
The critical rule is:
Retention must never delete the last usable recovery point merely to satisfy a numerical retention policy.
1. What We Need to Build
Create these components:
hosting-backup-schedule
hosting-backup-run
hosting-backup-prune
hosting-backup-status
The scheduler should eventually support:
daily
weekly
monthly
on-demand
repair
pre-restore
2. Don’t Use One Cron Entry Per Website
Avoid:
cron:
example.com
cron:
shop.example.com
cron:
blog.example.com
cron:
medical.example.com
That becomes difficult to manage.
Instead:
SYSTEM SCHEDULER
↓
CHP BACKUP SCHEDULER
↓
QUERY DATABASE
↓
FIND DUE SITES
↓
CREATE BACKUP JOBS
This centralizes scheduling.
3. Scheduler Architecture
systemd timer
│
▼
hosting-backup-scheduler
│
┌──────┴──────┐
▼ ▼
Database Policy
│ │
└──────┬──────┘
▼
DUE JOBS
│
▼
BACKUP ENGINE
│
┌─────────┴─────────┐
▼ ▼
SUCCESS FAIL
│ │
▼ ▼
RETENTION ALERT
4. Why systemd Timer?
Since the server is Ubuntu, use systemd for the scheduler rather than relying entirely on traditional cron.
Advantages:
logging
service state
failure tracking
restart behavior
central management
5. Scheduler Service
Create:
sudo nano /etc/systemd/system/cresignsys-backup-scheduler.service
Conceptually:
[Unit]
Description=CresignSys Backup Scheduler
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/hosting-backup-scheduler
6. Scheduler Timer
Create:
sudo nano /etc/systemd/system/cresignsys-backup-scheduler.timer
Example:
[Unit]
Description=Run CresignSys Backup Scheduler
[Timer]
OnCalendar=*:0/15
Persistent=true
[Install]
WantedBy=timers.target
This checks every 15 minutes.
The scheduler itself determines whether a particular site’s backup is actually due.
7. Why Check Frequently?
Suppose a site is configured:
Daily:
02:00
If the scheduler runs only once at exactly 02:00 and the server is temporarily unavailable, the backup could be missed.
With:
every 15 minutes
the scheduler can determine:
backup due?
yes
and execute it.
8. Persistent=true
This is useful when the server is offline.
For example:
02:00
server offline
05:00
server starts
The timer knows it missed a scheduled run.
The scheduler can then decide:
run missed backup
subject to policy.
9. Backup Policy Table
Create:
CREATE TABLE backup_policies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
site_id INTEGER NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
daily_enabled INTEGER NOT NULL DEFAULT 1,
daily_time TEXT,
weekly_enabled INTEGER NOT NULL DEFAULT 0,
weekly_day INTEGER,
weekly_time TEXT,
monthly_enabled INTEGER NOT NULL DEFAULT 0,
monthly_day INTEGER,
monthly_time TEXT,
retention_daily INTEGER NOT NULL DEFAULT 7,
retention_weekly INTEGER NOT NULL DEFAULT 4,
retention_monthly INTEGER NOT NULL DEFAULT 12,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (site_id)
REFERENCES sites(id)
ON DELETE CASCADE
);
10. Example Policy
For:
example.com
store:
Daily:
YES
Daily time:
02:00
Weekly:
YES
Weekly day:
Sunday
Weekly time:
03:00
Monthly:
YES
Monthly day:
1
Monthly time:
04:00
11. Retention Policy
Example:
Daily:
7
Weekly:
4
Monthly:
12
This does not mean:
delete everything older than 12 months
It means:
retain selected recovery points according to policy
12. Backup Classification
Every scheduled backup should be classified:
DAILY
WEEKLY
MONTHLY
Example:
BK-001
DAILY
BK-002
DAILY
BK-003
WEEKLY
The retention engine uses these classifications.
13. Backup Schedule Table
It can be useful to distinguish:
policy
from:
execution history
The policy says:
"Run every day at 02:00."
The backup record says:
"Actually ran at 02:04 and succeeded."
Don’t mix those concepts.
14. Add Scheduled Metadata
Add to backups:
ALTER TABLE backups
ADD COLUMN schedule_type TEXT;
Possible:
DAILY
WEEKLY
MONTHLY
MANUAL
REPAIR
PRE_RESTORE
15. Scheduler Command
Create:
sudo nano /usr/local/bin/hosting-backup-scheduler
Start:
#!/usr/bin/env bash
set -Eeuo pipefail
Load:
source /etc/cresignsys/hosting.conf
source /etc/cresignsys/lib/common.sh
source /etc/cresignsys/lib/logging.sh
source /etc/cresignsys/lib/database.sh
source /etc/cresignsys/lib/backup.sh
source /etc/cresignsys/lib/backup-policy.sh
16. Scheduler Algorithm
The scheduler should:
1. Find enabled sites
2. Load backup policy
3. Determine whether backup is due
4. Check whether another backup is running
5. Check storage availability
6. Create backup job
7. Execute backup
8. Record result
9. Trigger retention
10. Record scheduler result
17. Find Due Sites
Conceptually:
for each managed site:
load backup policy
if backup is due:
create backup job
Don’t execute backups for:
READ_ONLY
sites unless their backup policy explicitly permits monitoring-only backups.
18. Management State and Backups
This requires a distinction.
A site can be:
READ_ONLY
for configuration changes but still have:
BACKUP:
ENABLED
Therefore don’t automatically equate:
READ_ONLY
=
NO BACKUP
Backup is not necessarily a configuration modification.
19. Better Permission Model
Use separate capabilities:
MONITOR
BACKUP
MANAGE
RESTORE
APPROVE
Then:
READ_ONLY
could mean:
MONITOR:
YES
BACKUP:
YES
MANAGE:
NO
RESTORE:
NO
This is more flexible.
20. Backup Job Table
Create:
CREATE TABLE backup_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
site_id INTEGER NOT NULL,
schedule_type TEXT NOT NULL,
status TEXT NOT NULL,
scheduled_for TEXT NOT NULL,
started_at TEXT,
completed_at TEXT,
backup_id INTEGER,
error_message TEXT,
FOREIGN KEY (site_id)
REFERENCES sites(id),
FOREIGN KEY (backup_id)
REFERENCES backups(id)
);
21. Job States
Use:
SCHEDULED
RUNNING
SUCCESS
FAILED
SKIPPED
CANCELLED
Example:
02:00
SCHEDULED
02:03
RUNNING
02:12
SUCCESS
22. Why a Job Table?
Without it, you only know:
Backup exists.
With it, you know:
Was it scheduled?
Did it start?
Was it skipped?
Why did it fail?
How long did it take?
Which backup resulted?
This is essential for operations.
23. Duplicate Prevention
Suppose the scheduler runs at:
02:00
02:15
and the 02:00 backup is still running.
The 02:15 execution must detect:
BACKUP ALREADY RUNNING
and skip creating another backup.
24. Site Backup Lock
Use:
/var/lock/cresignsys/example.com.lock
The backup engine already uses this.
The scheduler should not bypass it.
25. Job Lock vs Site Lock
These solve different problems.
Job lock
Prevents duplicate scheduler execution.
Site lock
Prevents conflicting operations on the website.
You can have:
scheduler lock
+
site lock
26. Example
Scheduler A:
creates job 101
Scheduler B:
sees job 101 RUNNING
Scheduler B:
SKIPPED
This prevents duplicate backups.
27. Missed Backup Policy
Suppose:
scheduled:
02:00
server:
offline
server returns:
07:00
The scheduler sees:
MISSED
What should it do?
For the first version:
run once
if the backup has not already occurred that day.
28. Don’t Create Multiple Catch-Up Backups
If the server was offline for:
3 days
don’t create:
3 immediate backups
one after another.
Instead:
one catch-up backup
then return to the normal schedule.
29. Missed Backup Status
Record:
schedule_type:
DAILY
scheduled_for:
2026-08-13 02:00
actual:
2026-08-13 07:05
result:
SUCCESS
execution_reason:
MISSED_SCHEDULE
This is useful for reporting.
30. Backup Freshness
Site status can then calculate:
latest successful backup
and:
backup age
Example:
Latest:
07:05
Age:
2 hours
31. Backup SLA
A hosting plan can define:
backup freshness:
24 hours
Then:
age < 24h
HEALTHY
and:
age > 24h
WARNING
32. Backup SLA by Hosting Plan
This fits naturally with your hosting platform.
For example:
Starter:
daily
Business:
daily
Professional:
daily + weekly
Business Plus:
daily + weekly + monthly
The exact commercial policy can be configured later.
33. Storage Check
Before creating a backup:
df -P /var/lib/cresignsys/backups
Determine available storage.
If:
available:
5 GB
but estimated backup:
8 GB
don’t begin.
34. Estimate Backup Size
Use the site root:
du -sb "$WEB_ROOT"
plus:
database estimate
configuration
compression overhead
Then:
required space
should include a safety margin.
35. Safety Margin
Don’t require:
available >= estimated
Use something like:
available >= estimated × 1.5
for the first version.
This avoids failure when compression or temporary files require extra space.
36. What If Storage Is Low?
Don’t automatically delete backups before creating the new backup.
Instead:
CHECK
↓
RETENTION CANDIDATES
↓
VERIFY SAFE TO DELETE
↓
PRUNE
↓
RECHECK STORAGE
↓
BACKUP
But only if pruning does not violate recovery requirements.
37. Never Delete First, Think Later
Bad:
disk full
↓
delete oldest backups
↓
backup
The deletion could remove the only usable recovery point.
Better:
disk full
↓
calculate protected backups
↓
calculate safe candidates
↓
prune only safe candidates
↓
recheck
38. Protected Backup
A backup is protected if it is:
latest verified backup
or:
required by weekly policy
or:
required by monthly policy
or:
associated with an active repair
or:
currently being restored
39. Retention Algorithm
Suppose backups are:
D1
D2
D3
D4
D5
D6
D7
D8
D9
Policy:
retain 7 daily
Keep:
D3–D9
Candidates:
D1
D2
But if D2 is also the weekly recovery point:
D2:
PROTECTED
then D1 becomes the only deletion candidate.
40. Retention Is Not Simple FIFO
Never implement:
sort by date
delete oldest N
without classification.
Retention needs to understand:
daily
weekly
monthly
protected
verified
active
41. Example
Policy:
Daily:
7
Weekly:
4
Monthly:
12
A single backup can satisfy more than one role.
For example:
Sunday backup
can be:
DAILY
+
WEEKLY
The retention engine should avoid storing unnecessary duplicate copies where possible.
42. Retention Selection
Conceptually:
1. Select newest 7 daily backups
2. Select newest 4 weekly backups
3. Select newest 12 monthly backups
4. Combine selections
5. Mark all selected backups PROTECTED
6. Everything else becomes a candidate
Then apply safety checks.
43. Candidate States
Don’t immediately delete.
Use:
CANDIDATE
first.
Then:
SAFE_TO_DELETE
Then:
DELETED
This makes the retention process auditable.
44. Retention Table
Create:
CREATE TABLE backup_retention_actions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
backup_id INTEGER NOT NULL,
action TEXT NOT NULL,
reason TEXT,
created_at TEXT NOT NULL,
executed_at TEXT,
FOREIGN KEY (backup_id)
REFERENCES backups(id)
);
Example:
backup:
BK-001
action:
PROTECT
reason:
Newest daily backup
or:
backup:
BK-002
action:
DELETE
reason:
Older than daily retention
45. Never Delete Unverified Backups First
It might seem logical to delete:
FAILED
backups first.
But even a failed backup could contain useful partial data for forensic purposes.
Instead define a cleanup policy.
For example:
FAILED:
retain 24–72 hours
then remove automatically.
46. Corrupted Backup
If a previously verified backup later fails checksum verification:
VERIFIED
↓
CORRUPTED
It must immediately lose its protected status.
Then find another valid recovery point.
47. Last Known-Good Backup
For every site, maintain:
last_verified_backup_id
Conceptually:
site
↓
last known good backup
This can be used by:
repair rollback
restore recommendation
disaster recovery
site status
48. Add to Sites
Eventually:
ALTER TABLE sites
ADD COLUMN last_verified_backup_id INTEGER;
However, because SQLite foreign-key handling may require care when altering tables, implement this through your normal CHP database migration system rather than manually changing production tables repeatedly.
49. Better: Derived Query
Initially you don’t even need to store it.
Query:
latest backup
WHERE
site_id = ?
AND status = VERIFIED
ORDER BY verified_at DESC
LIMIT 1
This avoids duplicated state.
50. Backup Status
The site dashboard can show:
Backup:
HEALTHY
Last verified:
2026-08-13 07:05
Age:
2h 15m
If no verified backup exists:
Backup:
CRITICAL
51. Scheduler Logging
Every scheduled execution should log:
site
schedule
scheduled time
actual time
result
backup ID
duration
Example:
2026-08-13 07:05
example.com
DAILY
SUCCESS
BK-20260813-070501
duration=7m32s
52. Backup Duration
Record:
started_at
completed_at
duration_seconds
This allows detection of unusual changes.
For example:
Normal:
7 minutes
Current:
48 minutes
could indicate:
site growth
storage problem
database issue
network issue
53. Backup Performance
Over time, CHP can calculate:
average backup duration
backup size growth
database growth
storage consumption
This becomes useful for capacity planning.
54. Backup Job Failure
If:
hosting-backup
returns failure:
job:
FAILED
Do not run retention as though the backup succeeded.
Instead:
FAILED
↓
record failure
↓
alert
Retention can run independently later.
55. Retry Policy
A backup failure can be retried carefully.
For example:
attempt 1:
FAILED
wait:
30 minutes
attempt 2:
FAILED
wait:
60 minutes
attempt 3:
FAILED
Then:
FINAL FAILURE
Don’t retry indefinitely.
56. Why Limited Retry?
Possible causes include:
disk full
database locked
permission error
network storage unavailable
corrupt filesystem
Repeated retries can make the situation worse.
57. Backup Retry Table
The job record can include:
attempt_number
Example:
Job 101
Attempt 1:
FAILED
Attempt 2:
SUCCESS
58. Scheduler Concurrency
Suppose 20 websites are due at 02:00.
Don’t necessarily run:
20 backups
simultaneously
That could overload:
CPU
disk I/O
RAM
MySQL
59. Global Backup Concurrency
Configure:
max concurrent backups:
2
Initially.
Then:
Site A → RUNNING
Site B → RUNNING
Site C → QUEUED
Site D → QUEUED
60. Backup Queue
Eventually:
BACKUP QUEUE
-------------
A RUNNING
B RUNNING
C QUEUED
D QUEUED
E QUEUED
This becomes important when CHP hosts many sites.
61. Site Lock Still Applies
Even with a global queue:
site A
cannot have:
backup
+
restore
at the same time.
The site lock remains the final protection.
62. Scheduler Priorities
Eventually jobs can have:
CRITICAL
HIGH
NORMAL
LOW
For example:
PRE_RESTORE:
CRITICAL
REPAIR:
HIGH
CUSTOMER DAILY:
NORMAL
This prevents routine backups from blocking urgent recovery operations.
63. Backup Scheduling Database
The overall structure now becomes:
sites
│
├── backup_policies
│
├── backup_jobs
│
└── backups
│
└── retention_actions
This cleanly separates:
policy
execution
artifact
retention
64. Backup Scheduler Status
Create:
sudo nano /usr/local/bin/hosting-backup-status
Output:
CresignSys Backup Status
========================
Site:
example.com
Policy:
Daily 02:00
Weekly Sunday 03:00
Monthly 1st 04:00
Last Backup:
2026-08-13 07:05
Status:
VERIFIED
Next Scheduled:
2026-08-14 02:00
Retention:
7 daily
4 weekly
12 monthly
Storage:
42%
Scheduler:
HEALTHY
65. Site-Level Backup Status
For all sites:
sudo hosting-backup-status --all
Output:
SITE LAST BACKUP STATUS
-----------------------------------------------------
example.com 2h HEALTHY
shop.example.com 5h HEALTHY
blog.example.com 31h WARNING
client.example.com 4d CRITICAL
This will eventually feed the CHP dashboard.
66. Scheduler Health
The scheduler itself must be monitored.
Suppose:
systemd timer:
stopped
Then:
all sites
could silently stop receiving backups.
Therefore:
CHP scheduler health
must be part of the platform health model.
67. Scheduler Heartbeat
Record:
last_scheduler_run
For example:
2026-08-13 15:00
If current time is:
18:00
and no scheduler run has occurred:
SCHEDULER:
WARNING
68. Scheduler Failure
If the timer repeatedly fails:
SCHEDULER:
CRITICAL
This is different from:
individual backup:
FAILED
The distinction matters.
69. Three Levels
PLATFORM
│
├── Scheduler health
│
└── Site backup health
Example:
Scheduler:
HEALTHY
Site A:
HEALTHY
Site B:
FAILED
Site C:
HEALTHY
Only Site B has a backup problem.
70. Backup Alert Conditions
Eventually alert when:
no verified backup within SLA
or:
backup failed repeatedly
or:
storage critically low
or:
scheduler not running
or:
last verified backup corrupted
71. Do Not Alert on Every Scheduler Run
Bad:
Backup succeeded
Backup succeeded
Backup succeeded
The system should notify only when there is something requiring attention.
72. Backup Recovery Objective
Now we can introduce two important concepts.
RPO
Recovery Point Objective:
How much recent data can the customer afford to lose?
Example:
Daily backup:
RPO ≈ 24 hours
RTO
Recovery Time Objective:
How quickly can the website be restored?
These concepts will eventually influence hosting plans.
73. Example
Starter:
RPO:
24h
RTO:
best effort
Professional:
RPO:
24h
RTO:
priority restore
Premium:
RPO:
shorter interval
RTO:
priority recovery
Exact commercial values should be defined separately.
74. Backup Scheduler and CHP Repairs
Now consider:
02:00:
scheduled backup
02:10:
repair starts
The repair engine can use:
02:00 backup
if it meets the required safety conditions.
But a repair should preferably create its own:
REPAIR
backup immediately before modification.
75. Why Scheduled Backup Is Not Enough
A scheduled backup at:
02:00
does not protect against:
repair at 16:00
because the site may have changed significantly during the day.
Therefore:
scheduled backup
+
pre-repair backup
are different safety mechanisms.
76. Backup Types Summary
DAILY
customer recovery
WEEKLY
longer retention
MONTHLY
long-term recovery
REPAIR
immediate rollback
PRE_RESTORE
protection before restoration
MANUAL
operator-created recovery point
77. Retention Protection
The retention engine should protect:
latest verified DAILY
latest verified WEEKLY
latest verified MONTHLY
active REPAIR backup
active PRE_RESTORE backup
Only then can it identify deletion candidates.
78. Deletion Algorithm
LOAD ALL BACKUPS
↓
REMOVE ACTIVE/PROTECTED
↓
SELECT RETENTION SET
↓
MARK CANDIDATES
↓
VERIFY AT LEAST ONE GOOD RECOVERY POINT
↓
DELETE
↓
VERIFY STORAGE
79. Final Safety Check
Before deleting any backup:
verified_count_after_delete >= minimum_required_recovery_points
For example:
minimum:
1
Then CHP must never produce:
VERIFIED BACKUPS:
0
unless an administrator explicitly overrides the policy.
80. Never Delete the Last Verified Backup
This deserves its own rule:
if deleting backup would leave zero VERIFIED backups:
DENY deletion
Even if:
retention policy says delete
the safety rule wins.
81. Example
Suppose:
Backup A:
VERIFIED
Backup B:
FAILED
Backup C:
CORRUPTED
Retention says:
delete old backups
The engine must preserve:
Backup A
because it is the only known-good recovery point.
82. If Storage Is 100%
Suppose:
disk:
100%
and:
new backup:
cannot start
The system should:
1. identify safe deletion candidates
2. protect last verified backup
3. prune only safe candidates
4. retry backup
If insufficient space remains:
BACKUP FAILED
and alert.
Never delete the last recovery point just to make the new backup fit.
83. Backup Scheduler Final Architecture
SYSTEMD TIMER
│
▼
BACKUP SCHEDULER
│
┌─────────┴─────────┐
▼ ▼
DUE JOBS MISSED JOBS
│ │
└─────────┬─────────┘
▼
JOB QUEUE
│
▼
BACKUP ENGINE
│
┌──────────┴──────────┐
▼ ▼
SUCCESS FAIL
│ │
▼ ▼
RETENTION RETRY
│ │
▼ ▼
SAFE PRUNE ALERT
84. Complete CHP Architecture So Far
CRESIGNSYS CHP
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
DISCOVERY OBSERVATION CONTROL
│ │ │
▼ ▼ ▼
DB IMPORT RECONCILIATION REPAIR PLAN
│ │
▼ ▼
HEALTH APPROVAL
│
▼
REPAIR
│
▼
BACKUP
│
▼
VERIFY
And backup itself now has:
POLICY
↓
SCHEDULER
↓
JOB
↓
BACKUP
↓
VERIFY
↓
RETENTION
85. Lesson 088 — Core Principle
A professional backup scheduler does not simply say:
"Run backup every day."
It must understand:
WHEN should it run?
WAS it actually run?
DID it succeed?
IS the backup verified?
IS it still useful?
IS it protected by retention?
IS there enough storage?
IS another backup running?
IS the scheduler healthy?
IS there at least one recovery point?
The critical retention rule remains:
NEVER
↓
delete the last
known-good backup
Next Lesson — 089
Build the CHP Central Job Queue
We now have several independent operations:
BACKUP
RESTORE
REPAIR
HEALTH CHECK
RECONCILIATION
RETENTION
Running all of them independently will eventually create conflicts.
The next step is therefore a central:
CHP JOB ENGINE
with:
QUEUED
RUNNING
SUCCESS
FAILED
CANCELLED
and controlled concurrency:
CHP JOB QUEUE
│
┌────────────┼────────────┐
▼ ▼ ▼
BACKUP REPAIR RESTORE
│ │ │
└────────────┼────────────┘
▼
WORKER ENGINE
│
┌─────────┴─────────┐
▼ ▼
RUNNING QUEUED
│
▼
RESULT
This will allow CHP to prevent dangerous situations such as:
BACKUP + RESTORE
REPAIR + RESTORE
REPAIR + REPAIR
running against the same website simultaneously.
Leave a Reply