From 546cb11e271485cc05b463949816aaaf9d259de3 Mon Sep 17 00:00:00 2001 From: Nikan Zeyaei Date: Sat, 27 Jun 2026 12:58:15 +0330 Subject: [PATCH] feat(backup): prefix backup filenames with date and time --- internal/web/service/backup_filename_test.go | 11 +++++------ internal/web/service/server.go | 11 ++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/web/service/backup_filename_test.go b/internal/web/service/backup_filename_test.go index f3b879714..a67b59f89 100644 --- a/internal/web/service/backup_filename_test.go +++ b/internal/web/service/backup_filename_test.go @@ -38,8 +38,8 @@ func TestSanitizeBackupHost(t *testing.T) { } } -// datePrefixRegex narrows backupFilenameRegex to the exact YYYY-MM-DD_ shape. -var datePrefixRegex = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}_$`) +// datePrefixRegex narrows backupFilenameRegex to the exact YYYY-MM-DD_HHMMSS_ shape. +var datePrefixRegex = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}_\d{6}_$`) func TestBackupDatePrefix(t *testing.T) { cases := []struct { @@ -47,9 +47,9 @@ func TestBackupDatePrefix(t *testing.T) { now time.Time want string }{ - {"utc midnight", time.Date(2026, 6, 27, 0, 0, 0, 0, time.UTC), "2026-06-27_"}, - {"end of year", time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC), "2025-12-31_"}, - {"single digit month/day padded", time.Date(2026, 1, 5, 9, 4, 0, 0, time.UTC), "2026-01-05_"}, + {"utc midnight", time.Date(2026, 6, 27, 0, 0, 0, 0, time.UTC), "2026-06-27_000000_"}, + {"end of year", time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC), "2025-12-31_235959_"}, + {"single digit month/day padded", time.Date(2026, 1, 5, 9, 4, 0, 0, time.UTC), "2026-01-05_090400_"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -60,7 +60,6 @@ func TestBackupDatePrefix(t *testing.T) { if !datePrefixRegex.MatchString(got) { t.Errorf("backupDatePrefix(%v) = %q, not a valid date prefix", tc.now, got) } - // The prefix must also satisfy the controller's filename safety regex. if !backupFilenameRegex.MatchString(got) { t.Errorf("backupDatePrefix(%v) = %q, not a valid download filename char", tc.now, got) } diff --git a/internal/web/service/server.go b/internal/web/service/server.go index ca11c4e89..a1425b352 100644 --- a/internal/web/service/server.go +++ b/internal/web/service/server.go @@ -1297,8 +1297,9 @@ func (s *ServerService) GetDb() ([]byte, error) { } // BackupFilename returns the filename for a database backup, prefixed with the -// current date (YYYY-MM-DD_) so files accumulated in Telegram chat history sort -// chronologically, and named after the panel's address so a downloaded or +// current date and time (YYYY-MM-DD_HHMMSS_) so files accumulated in Telegram +// chat history sort chronologically and same-day backups stay distinct, and +// named after the panel's address so a downloaded or // Telegram-sent backup identifies the server it came from. requestHost is the // browser's address: the getDb handler passes c.Request.Host so a panel download // is named after whatever address the user reached the panel with, no Listen @@ -1314,11 +1315,11 @@ func (s *ServerService) BackupFilename(requestHost string) string { return backupDatePrefix(time.Now()) + s.backupHost(requestHost) + ext } -// backupDatePrefix returns the YYYY-MM-DD_ chronological-sort prefix prepended to -// backup filenames. The date uses server-local time for consistency with the +// backupDatePrefix returns the YYYY-MM-DD_HHMMSS_ chronological-sort prefix +// prepended to backup filenames. Uses server-local time for consistency with the // timestamp printed in the Telegram backup message body. func backupDatePrefix(now time.Time) string { - return now.Format("2006-01-02") + "_" + return now.Format("2006-01-02_150405") + "_" } // backupHost picks the address used to name backup files: the browser's request