feat(backup): take Tinyforge DB snapshot before every deploy
Adds an opt-in "auto_backup_before_deploy" setting that triggers a "pre-deploy" backup at the start of every project deploy via the deploy pipeline (covers both the async HTTP path and the sync poller/webhook path). Failures are logged to the deploy log but do not abort — missing a backup is preferable to refusing to ship a fix. - store: settings.auto_backup_before_deploy column + scan/update wiring - backup: accept "pre-deploy" as a valid backup_type - deployer: small PreDeployBackuper interface, hooked into runDeploy right after settings load and before any state-mutating work - api: settings request/response surface the new flag - web: ToggleSwitch on the backup settings page; "Pre-deploy" badge variant in the backup list (badge-warning so it stands out) - i18n: en/ru strings for the toggle, help text, and badge label
This commit is contained in:
@@ -82,6 +82,10 @@ type Settings struct {
|
||||
BackupEnabled bool `json:"backup_enabled"`
|
||||
BackupIntervalHours int `json:"backup_interval_hours"`
|
||||
BackupRetentionCount int `json:"backup_retention_count"`
|
||||
// AutoBackupBeforeDeploy creates a "pre-deploy" Tinyforge DB backup
|
||||
// at the start of every project deploy. Independent of BackupEnabled
|
||||
// (which governs the periodic auto-backup cron).
|
||||
AutoBackupBeforeDeploy bool `json:"auto_backup_before_deploy"`
|
||||
StatsIntervalSeconds int `json:"stats_interval_seconds"` // 0 disables collection
|
||||
StatsRetentionHours int `json:"stats_retention_hours"` // 0 disables collection
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
// GetSettings returns the global settings (single-row pattern, always row id=1).
|
||||
func (s *Store) GetSettings() (Settings, error) {
|
||||
var st Settings
|
||||
var wildcardDNS, npmRemote, backupEnabled int
|
||||
var wildcardDNS, npmRemote, backupEnabled, autoBackupBeforeDeploy int
|
||||
err := s.db.QueryRow(
|
||||
`SELECT domain, server_ip, public_ip, network, subdomain_pattern, notification_url,
|
||||
notification_secret,
|
||||
@@ -19,6 +19,7 @@ func (s *Store) GetSettings() (Settings, error) {
|
||||
traefik_entrypoint, traefik_cert_resolver, traefik_network, traefik_api_url,
|
||||
image_prune_threshold_mb,
|
||||
backup_enabled, backup_interval_hours, backup_retention_count,
|
||||
auto_backup_before_deploy,
|
||||
stats_interval_seconds, stats_retention_hours,
|
||||
updated_at
|
||||
FROM settings WHERE id = 1`,
|
||||
@@ -32,6 +33,7 @@ func (s *Store) GetSettings() (Settings, error) {
|
||||
&st.TraefikEntrypoint, &st.TraefikCertResolver, &st.TraefikNetwork, &st.TraefikAPIURL,
|
||||
&st.ImagePruneThresholdMB,
|
||||
&backupEnabled, &st.BackupIntervalHours, &st.BackupRetentionCount,
|
||||
&autoBackupBeforeDeploy,
|
||||
&st.StatsIntervalSeconds, &st.StatsRetentionHours,
|
||||
&st.UpdatedAt)
|
||||
if err != nil {
|
||||
@@ -40,6 +42,7 @@ func (s *Store) GetSettings() (Settings, error) {
|
||||
st.WildcardDNS = wildcardDNS != 0
|
||||
st.NpmRemote = npmRemote != 0
|
||||
st.BackupEnabled = backupEnabled != 0
|
||||
st.AutoBackupBeforeDeploy = autoBackupBeforeDeploy != 0
|
||||
return st, nil
|
||||
}
|
||||
|
||||
@@ -58,6 +61,10 @@ func (s *Store) UpdateSettings(st Settings) error {
|
||||
if st.BackupEnabled {
|
||||
backupEnabled = 1
|
||||
}
|
||||
autoBackupBeforeDeploy := 0
|
||||
if st.AutoBackupBeforeDeploy {
|
||||
autoBackupBeforeDeploy = 1
|
||||
}
|
||||
_, err := s.db.Exec(
|
||||
`UPDATE settings SET
|
||||
domain=?, server_ip=?, public_ip=?, network=?, subdomain_pattern=?, notification_url=?,
|
||||
@@ -70,6 +77,7 @@ func (s *Store) UpdateSettings(st Settings) error {
|
||||
traefik_entrypoint=?, traefik_cert_resolver=?, traefik_network=?, traefik_api_url=?,
|
||||
image_prune_threshold_mb=?,
|
||||
backup_enabled=?, backup_interval_hours=?, backup_retention_count=?,
|
||||
auto_backup_before_deploy=?,
|
||||
stats_interval_seconds=?, stats_retention_hours=?,
|
||||
updated_at=?
|
||||
WHERE id = 1`,
|
||||
@@ -83,6 +91,7 @@ func (s *Store) UpdateSettings(st Settings) error {
|
||||
st.TraefikEntrypoint, st.TraefikCertResolver, st.TraefikNetwork, st.TraefikAPIURL,
|
||||
st.ImagePruneThresholdMB,
|
||||
backupEnabled, st.BackupIntervalHours, st.BackupRetentionCount,
|
||||
autoBackupBeforeDeploy,
|
||||
st.StatsIntervalSeconds, st.StatsRetentionHours,
|
||||
st.UpdatedAt,
|
||||
)
|
||||
|
||||
@@ -147,6 +147,10 @@ func (s *Store) runMigrations() error {
|
||||
`ALTER TABLE stages ADD COLUMN notification_secret TEXT NOT NULL DEFAULT ''`,
|
||||
`ALTER TABLE static_sites ADD COLUMN notification_url TEXT NOT NULL DEFAULT ''`,
|
||||
`ALTER TABLE static_sites ADD COLUMN notification_secret TEXT NOT NULL DEFAULT ''`,
|
||||
// Auto-backup before deploy (2026-05-07). When enabled, the deployer
|
||||
// triggers a "pre-deploy" Tinyforge DB backup before any project deploy
|
||||
// so a corrupted deploy is recoverable without data loss.
|
||||
`ALTER TABLE settings ADD COLUMN auto_backup_before_deploy INTEGER NOT NULL DEFAULT 0`,
|
||||
}
|
||||
|
||||
// Additive stack tables (2026-04-16). Created here rather than in the
|
||||
|
||||
Reference in New Issue
Block a user