feat(restore): cross-platform restore via dump engine rebuild (#606) (#611)

* feat(restore): cross-platform restore via dump engine rebuild (#606)

Restore previously required the dump's origin OS, overlaying keys onto locally-discovered browsers. It now rebuilds Chromium engines from the dump's vaults (v2 adds engine kind), so copied data or an archive zip decrypts on any OS.

* fix(restore): polish help text, drop dead check, dedup dump kinds

pflag treats backticked words in flag usage as the value placeholder,
so --data-zip rendered as "--data-zip archive" in help output.
This commit is contained in:
Roger
2026-06-12 20:53:00 +08:00
committed by GitHub
parent 8d8bd81790
commit bf96ba8c80
11 changed files with 493 additions and 189 deletions
+20 -6
View File
@@ -54,9 +54,10 @@ func NewBrowser(cfg types.BrowserConfig) (*Browser, error) {
// Extract; unused tiers stay nil.
func (b *Browser) SetRetrievers(r masterkey.Retrievers) { b.retrievers = r }
func (b *Browser) BrowserName() string { return b.cfg.Name }
func (b *Browser) BrowserKey() string { return b.cfg.Key }
func (b *Browser) UserDataDir() string { return b.cfg.UserDataDir }
func (b *Browser) BrowserName() string { return b.cfg.Name }
func (b *Browser) BrowserKey() string { return b.cfg.Key }
func (b *Browser) UserDataDir() string { return b.cfg.UserDataDir }
func (b *Browser) Kind() types.BrowserKind { return b.cfg.Kind }
// Profiles returns the identity of every profile in this installation.
func (b *Browser) Profiles() []types.Profile {
@@ -162,12 +163,25 @@ func discoverProfiles(userDataDir string, sources map[types.Category][]sourcePat
}
}
// Flat layout fallback (older Opera): data files directly in userDataDir.
// Opera stores data alongside Local State in userDataDir itself, so check
// for any known source file instead of Preferences.
// Flat layout (older Opera): data files directly under userDataDir with no profile subdir. Check the
// root before the subdir fallback so a stray source-bearing subdir can't suppress root discovery.
if len(profiles) == 0 && hasAnySource(sources, userDataDir) {
profiles = append(profiles, userDataDir)
}
// Restored/copied trees may omit the Preferences marker (it is no extraction source). When the marker
// scan and flat-layout check both find nothing, treat any source-bearing subdir as a profile.
if len(profiles) == 0 {
for _, e := range entries {
if !e.IsDir() || isSkippedDir(e.Name()) {
continue
}
dir := filepath.Join(userDataDir, e.Name())
if hasAnySource(sources, dir) {
profiles = append(profiles, dir)
}
}
}
return profiles
}
+44
View File
@@ -237,6 +237,50 @@ func TestNewBrowsers(t *testing.T) {
}
}
// ---------------------------------------------------------------------------
// discoverProfiles: fallback boundaries (marker-less copies, flat-layout precedence)
// ---------------------------------------------------------------------------
func TestDiscoverProfiles(t *testing.T) {
t.Run("markerless multi-subdir resolves all source-bearing dirs", func(t *testing.T) {
dir := t.TempDir()
mkFile(dir, "Default", "History")
mkFile(dir, "Profile 1", "History")
assert.Len(t, discoverProfiles(dir, chromiumSources), 2)
})
t.Run("marker present keeps fallback dormant", func(t *testing.T) {
dir := t.TempDir()
mkFile(dir, "Default", "Preferences")
mkFile(dir, "Default", "History")
mkFile(dir, "NotAProfile", "History")
got := discoverProfiles(dir, chromiumSources)
require.Len(t, got, 1)
assert.Equal(t, filepath.Join(dir, "Default"), got[0])
})
t.Run("skipped dir ignored by markerless fallback", func(t *testing.T) {
dir := t.TempDir()
mkFile(dir, "System Profile", "History")
assert.Empty(t, discoverProfiles(dir, chromiumSources))
})
t.Run("sourceless subdir ignored", func(t *testing.T) {
dir := t.TempDir()
mkDir(dir, "Crashpad")
assert.Empty(t, discoverProfiles(dir, chromiumSources))
})
t.Run("flat-layout root wins over source-bearing subdir", func(t *testing.T) {
dir := t.TempDir()
mkFile(dir, "History")
mkFile(dir, "Subthing", "History")
got := discoverProfiles(dir, chromiumSources)
require.Len(t, got, 1)
assert.Equal(t, dir, got[0], "flat-layout root must win; subdir must not hijack discovery")
})
}
// ---------------------------------------------------------------------------
// Test helpers
// ---------------------------------------------------------------------------