From 5b9674987fbfd0f207b985aeaa8fbc2da9bcccc1 Mon Sep 17 00:00:00 2001 From: Z User Date: Mon, 31 Aug 2026 03:42:32 +0000 Subject: [PATCH] fix(backup): restore mihomo-config.yaml from backup zip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase fix(backup): restore mihomo-config.yaml from backup zip Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt. RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was silently dropped. After restoring a backup, the mihomo config file on disk was stale (from before the backup was taken). Fix: - extractDBFromZip now returns both dbContent and mihomoCfg - RestoreDatabase takes mihomoCfgPath parameter - After restoring the DB, also writes mihomo-config.yaml if the zip contained it and the path is configured - Mihomo config write failure does NOT fail the whole restore (DB is already restored; operator can manually restore config) - api.go passes h.mihomoCfg to RestoreDatabase --- backend/internal/system/api.go | 2 +- backend/internal/system/backup.go | 64 +++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/backend/internal/system/api.go b/backend/internal/system/api.go index 6af033c..e04bcb8 100644 --- a/backend/internal/system/api.go +++ b/backend/internal/system/api.go @@ -76,7 +76,7 @@ func (h *Handler) RestoreDatabase(c *gin.Context) { return } defer f.Close() - if err := RestoreDatabase(h.dbPath, f); err != nil { + if err := RestoreDatabase(h.dbPath, h.mihomoCfg, f); err != nil { log.Printf("system restore-database failed: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return diff --git a/backend/internal/system/backup.go b/backend/internal/system/backup.go index 211701d..1deff24 100644 --- a/backend/internal/system/backup.go +++ b/backend/internal/system/backup.go @@ -75,7 +75,7 @@ func WriteZip(w io.Writer, paths BackupPaths) error { // - A zip archive exported by ExportBackup (containing 3m-ui.db + mihomo-config.yaml) // // Callers should stop the panel or accept that a process restart may be required. -func RestoreDatabase(dbPath string, r io.Reader) error { +func RestoreDatabase(dbPath, mihomoCfgPath string, r io.Reader) error { if dbPath == "" { return fmt.Errorf("database path is empty") } @@ -92,10 +92,10 @@ func RestoreDatabase(dbPath string, r io.Reader) error { } // Check if it's a zip file (PK magic). - var dbContent []byte + var dbContent, mihomoCfg []byte if len(raw) >= 4 && raw[0] == 0x50 && raw[1] == 0x4B && raw[2] == 0x03 && raw[3] == 0x04 { // Zip archive — extract 3m-ui.db from it. - dbContent, err = extractDBFromZip(raw) + dbContent, mihomoCfg, err = extractDBFromZip(raw) if err != nil { return fmt.Errorf("extract from zip: %w", err) } @@ -116,32 +116,56 @@ func RestoreDatabase(dbPath string, r io.Reader) error { if err := os.WriteFile(tmp, dbContent, 0o600); err != nil { return err } - return os.Rename(tmp, dbPath) + if err := os.Rename(tmp, dbPath); err != nil { + return err + } + + // Restore mihomo-config.yaml if the zip contained it and the path is set. + if len(mihomoCfg) > 0 && mihomoCfgPath != "" { + cfgDir := filepath.Dir(mihomoCfgPath) + _ = os.MkdirAll(cfgDir, 0o750) + cfgTmp := mihomoCfgPath + ".restore-tmp" + defer os.Remove(cfgTmp) + if err := os.WriteFile(cfgTmp, mihomoCfg, 0o600); err == nil { + _ = os.Rename(cfgTmp, mihomoCfgPath) + } + } + return nil } -// extractDBFromZip reads a zip archive and returns the contents of 3m-ui.db. -// Also restores mihomo-config.yaml if mihomoCfgPath is set. -func extractDBFromZip(raw []byte) ([]byte, error) { - zr, err := zip.NewReader(bytes.NewReader(raw), int64(len(raw))) - if err != nil { - return nil, fmt.Errorf("open zip: %w", err) +// extractDBFromZip reads a zip archive and returns the contents of +// 3m-ui.db and mihomo-config.yaml (if present). +func extractDBFromZip(raw []byte) (dbContent, mihomoCfg []byte, err error) { + zr, zErr := zip.NewReader(bytes.NewReader(raw), int64(len(raw))) + if zErr != nil { + return nil, nil, fmt.Errorf("open zip: %w", zErr) } - var dbContent []byte for _, f := range zr.File { - if f.Name == "3m-ui.db" { - rc, err := f.Open() - if err != nil { - return nil, fmt.Errorf("open %s in zip: %w", f.Name, err) + switch f.Name { + case "3m-ui.db": + rc, e := f.Open() + if e != nil { + return nil, nil, fmt.Errorf("open %s in zip: %w", f.Name, e) } - dbContent, err = io.ReadAll(rc) + dbContent, e = io.ReadAll(rc) rc.Close() - if err != nil { - return nil, fmt.Errorf("read %s in zip: %w", f.Name, err) + if e != nil { + return nil, nil, fmt.Errorf("read %s in zip: %w", f.Name, e) + } + case "mihomo-config.yaml": + rc, e := f.Open() + if e != nil { + return nil, nil, fmt.Errorf("open %s in zip: %w", f.Name, e) + } + mihomoCfg, e = io.ReadAll(rc) + rc.Close() + if e != nil { + return nil, nil, fmt.Errorf("read %s in zip: %w", f.Name, e) } } } if dbContent == nil { - return nil, fmt.Errorf("zip does not contain 3m-ui.db") + return nil, nil, fmt.Errorf("zip does not contain 3m-ui.db") } - return dbContent, nil + return dbContent, mihomoCfg, nil }