mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-29 06:27:14 +00:00
feat(backup): prefix backup filenames with date and time (#5606)
* feat(backup): add YYYY-MM-DD_ date prefix to backup filenames Refs #5584 * feat(backup): prefix backup filenames with date and time * fix(backup): put host before date in backup filename Backup filenames now read {host}_{date}{ext} (e.g. panel.example.com_2026-06-27_000000.db) instead of {date}_{host}{ext}, so files group by server first then sort chronologically within each server.
This commit is contained in:
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getDb (controller) only accepts a Content-Disposition filename matching this
|
// getDb (controller) only accepts a Content-Disposition filename matching this
|
||||||
@@ -36,3 +37,32 @@ func TestSanitizeBackupHost(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dateSuffixRegex narrows backupFilenameRegex to the exact _YYYY-MM-DD_HHMMSS shape.
|
||||||
|
var dateSuffixRegex = regexp.MustCompile(`^_\d{4}-\d{2}-\d{2}_\d{6}$`)
|
||||||
|
|
||||||
|
func TestBackupDateSuffix(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
now time.Time
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"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) {
|
||||||
|
got := backupDateSuffix(tc.now)
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("backupDateSuffix(%v) = %q, want %q", tc.now, got, tc.want)
|
||||||
|
}
|
||||||
|
if !dateSuffixRegex.MatchString(got) {
|
||||||
|
t.Errorf("backupDateSuffix(%v) = %q, not a valid date suffix", tc.now, got)
|
||||||
|
}
|
||||||
|
if !backupFilenameRegex.MatchString(got) {
|
||||||
|
t.Errorf("backupDateSuffix(%v) = %q, not a valid download filename char", tc.now, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1298,18 +1298,28 @@ func (s *ServerService) GetDb() ([]byte, error) {
|
|||||||
|
|
||||||
// BackupFilename returns the filename for a database backup, named after the
|
// BackupFilename returns the filename for a database backup, named after the
|
||||||
// panel's address so a downloaded or Telegram-sent backup identifies the server
|
// 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
|
// it came from, followed by the current date and time (_YYYY-MM-DD_HHMMSS) so
|
||||||
// c.Request.Host so a panel download is named after whatever address the user
|
// files accumulated in Telegram chat history group by server then sort
|
||||||
// reached the panel with, no Listen Domain needed. The Telegram bot has no
|
// chronologically and same-day backups stay distinct. requestHost is the
|
||||||
// request and passes "", falling back to the configured Listen Domain (webDomain)
|
// browser's address: the getDb handler passes c.Request.Host so a panel download
|
||||||
// and then the public IP. The extension is .dump on PostgreSQL and .db on SQLite;
|
// is named after whatever address the user reached the panel with, no Listen
|
||||||
// the base falls back to "x-ui" when no address is known.
|
// Domain needed. The Telegram bot has no request and passes "", falling back to
|
||||||
|
// the configured Listen Domain (webDomain) and then the public IP. The extension
|
||||||
|
// is .dump on PostgreSQL and .db on SQLite; the base falls back to "x-ui" when
|
||||||
|
// no address is known.
|
||||||
func (s *ServerService) BackupFilename(requestHost string) string {
|
func (s *ServerService) BackupFilename(requestHost string) string {
|
||||||
ext := ".db"
|
ext := ".db"
|
||||||
if database.IsPostgres() {
|
if database.IsPostgres() {
|
||||||
ext = ".dump"
|
ext = ".dump"
|
||||||
}
|
}
|
||||||
return s.backupHost(requestHost) + ext
|
return s.backupHost(requestHost) + backupDateSuffix(time.Now()) + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
// backupDateSuffix returns the _YYYY-MM-DD_HHMMSS chronological suffix appended
|
||||||
|
// after the host in backup filenames. Uses server-local time for consistency
|
||||||
|
// with the timestamp printed in the Telegram backup message body.
|
||||||
|
func backupDateSuffix(now time.Time) string {
|
||||||
|
return "_" + now.Format("2006-01-02_150405")
|
||||||
}
|
}
|
||||||
|
|
||||||
// backupHost picks the address used to name backup files: the browser's request
|
// backupHost picks the address used to name backup files: the browser's request
|
||||||
|
|||||||
Reference in New Issue
Block a user