From 9a1b590aae809d8020d39211b06c23f12e40eec0 Mon Sep 17 00:00:00 2001 From: hayzamjs Date: Thu, 21 May 2026 14:04:05 +0530 Subject: [PATCH] backups: zfs: fix encryption handling post OOB restore --- internal/services/zelta/encryption.go | 53 +++++++- internal/services/zelta/replication.go | 9 +- internal/services/zelta/restore.go | 10 +- .../DataCenter/Backups/Jobs/Form.svelte | 119 +++++++++++++----- web/src/locales/cs.po | 34 +++++ web/src/locales/de.po | 34 +++++ web/src/locales/en.po | 34 +++++ web/src/locales/hi.po | 34 +++++ web/src/locales/mal.po | 34 +++++ web/src/locales/zh-CN.po | 34 +++++ 10 files changed, 347 insertions(+), 48 deletions(-) diff --git a/internal/services/zelta/encryption.go b/internal/services/zelta/encryption.go index 447aa60a..7b006190 100644 --- a/internal/services/zelta/encryption.go +++ b/internal/services/zelta/encryption.go @@ -121,17 +121,58 @@ func (s *Service) EnsureEncryptionKeyFile(uuid string) error { return nil } -func (s *Service) ensureEncryptionKeyForDataset(ctx context.Context, ds *gzfs.Dataset) error { +func (s *Service) ensureEncryptionKeyForDataset(ctx context.Context, ds *gzfs.Dataset) (keyLoaded bool, err error) { keylocProp, err := ds.GetProperty(ctx, "keylocation") if err != nil { - return fmt.Errorf("get_keylocation_failed: %w", err) + return false, fmt.Errorf("get_keylocation_failed: %w", err) } keyloc := strings.TrimSpace(keylocProp.Value) - if keyloc == "" || keyloc == "none" || !strings.HasPrefix(keyloc, "file://") { - return fmt.Errorf("unexpected_keylocation: %s", keyloc) + if keyloc == "" || keyloc == "none" { + return false, nil } - uuid := filepath.Base(strings.TrimPrefix(keyloc, "file://")) - return s.EnsureEncryptionKeyFile(uuid) + if strings.HasPrefix(keyloc, "file://") { + uuid := filepath.Base(strings.TrimPrefix(keyloc, "file://")) + if err := s.EnsureEncryptionKeyFile(uuid); err != nil { + return false, fmt.Errorf("encryption_key_not_found_in_cluster_store: %s", uuid) + } + if err := ds.LoadKey(ctx, false); err != nil { + return false, fmt.Errorf("load_key_failed: %w", err) + } + return true, nil + } + + // keylocation is "prompt" — the original key file wasn't available on + // the server that received this dataset (e.g. a backup target). Try each + // key in the cluster store; match by attempting to load. + keys, listErr := s.Cluster.ListEncryptionKeys() + if listErr != nil { + logger.L.Warn().Err(listErr).Str("dataset", ds.Name). + Msg("encrypted_dataset_cannot_list_keys_for_prompt_keylocation") + return false, nil + } + + for _, key := range keys { + if err := ds.LoadKeyWithPassphrase(ctx, key.KeyData); err != nil { + continue + } + + // Key loaded — materialize the key file and fix keylocation + // so it auto-loads on next boot. + if writeErr := s.EnsureEncryptionKeyFile(key.UUID); writeErr != nil { + logger.L.Warn().Err(writeErr).Str("uuid", key.UUID). + Str("dataset", ds.Name).Msg("prompt_key_loaded_but_file_write_failed") + } else { + _ = ds.SetProperties(ctx, "keylocation", "file://"+filepath.Join(EncryptionKeyDirectory, key.UUID)) + } + + logger.L.Info().Str("dataset", ds.Name).Str("uuid", key.UUID). + Msg("encrypted_dataset_key_loaded_via_prompt_fallback") + return true, nil + } + + logger.L.Warn().Str("dataset", ds.Name). + Msg("encrypted_dataset_requires_manual_key_load_run_zfs_load_key") + return false, nil } diff --git a/internal/services/zelta/replication.go b/internal/services/zelta/replication.go index 54872cd2..595c3246 100644 --- a/internal/services/zelta/replication.go +++ b/internal/services/zelta/replication.go @@ -3707,11 +3707,12 @@ func (s *Service) prepareReplicatedDatasetForActivation(ctx context.Context, roo } if ds.IsEncrypted() { - if err := s.ensureEncryptionKeyForDataset(ctx, ds); err != nil { - return fmt.Errorf("replication_encryption_key_missing_%s: %w", dataset, err) + keyLoaded, err := s.ensureEncryptionKeyForDataset(ctx, ds) + if err != nil { + return fmt.Errorf("replication_encryption_key_failed_%s: %w", dataset, err) } - if err := ds.LoadKey(ctx, false); err != nil { - return fmt.Errorf("replication_load_key_failed_%s: %w", dataset, err) + if !keyLoaded { + logger.L.Warn().Str("dataset", dataset).Msg("replication_encryption_key_not_auto_loaded") } } diff --git a/internal/services/zelta/restore.go b/internal/services/zelta/restore.go index ab54fc52..6603ef80 100644 --- a/internal/services/zelta/restore.go +++ b/internal/services/zelta/restore.go @@ -358,13 +358,13 @@ func (s *Service) fixRestoredProperties(ctx context.Context, dataset string) { } if ds.IsEncrypted() { - if err := s.ensureEncryptionKeyForDataset(ctx, ds); err != nil { - logger.L.Error().Err(err).Str("dataset", dataset).Msg("fix_restored_property_key_missing") + keyLoaded, err := s.ensureEncryptionKeyForDataset(ctx, ds) + if err != nil { + logger.L.Error().Err(err).Str("dataset", dataset).Msg("fix_restored_property_key_load_failed") return } - if err := ds.LoadKey(ctx, false); err != nil { - logger.L.Error().Err(err).Str("dataset", dataset).Msg("fix_restored_property_load_key_failed") - return + if !keyLoaded { + logger.L.Warn().Str("dataset", dataset).Msg("fix_restored_property_key_not_auto_loaded") } } diff --git a/web/src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte b/web/src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte index a2fc18b6..3d1c8c68 100644 --- a/web/src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +++ b/web/src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte @@ -24,6 +24,7 @@ import { vmBaseDataset, vmStoragePools } from '$lib/utils/vm/vm'; import { watch } from 'runed'; import { toast } from 'svelte-sonner'; + import { sleep } from '$lib/utils'; interface Props { open: boolean; @@ -172,11 +173,18 @@ } srcEncryptionChecking = true; + await sleep(1000); + try { - const datasets = await getDatasets(GZFSDatasetTypeSchema.enum.ALL); + const datasets = await Promise.all([ + getDatasets(GZFSDatasetTypeSchema.enum.FILESYSTEM), + getDatasets(GZFSDatasetTypeSchema.enum.VOLUME) + ]).then(([filesystems, volumes]) => [...filesystems, ...volumes]); + const match = datasets.find((d) => { if (d.name !== dataset) return false; - const enc = d.properties?.encryption?.value; + console.log(d.properties); + const enc = d.properties?.encryption || ''; return enc && enc !== 'off' && enc !== '-' && enc !== 'none'; }); srcEncrypted = !!match; @@ -460,19 +468,42 @@ } }); - watch([() => open, () => form.mode, () => form.sourceDataset, () => form.selectedJailId, () => form.selectedVmId], ([isOpen]) => { - if (!isOpen) return; - let dataset = ''; - if (form.mode === 'dataset') { - dataset = form.sourceDataset; - } else if (form.mode === 'jail' && selectedJail) { - const base = selectedJail.storages?.find((s) => s.isBase); - if (base) dataset = `${base.pool}/sylve/jails/${selectedJail.ctId}`; - } else if (form.mode === 'vm' && selectedVM) { - dataset = vmBaseDataset(selectedVM) || ''; + watch( + [ + () => open, + () => form.mode, + () => form.sourceDataset, + () => form.selectedJailId, + () => form.selectedVmId + ], + ([isOpen]) => { + if (!isOpen) return; + let dataset = ''; + if (form.mode === 'dataset') { + dataset = form.sourceDataset; + } else if (form.mode === 'jail' && selectedJail) { + const base = selectedJail.storages?.find((s) => s.isBase); + if (base) dataset = `${base.pool}/sylve/jails/${selectedJail.ctId}`; + } else if (form.mode === 'vm' && selectedVM) { + dataset = vmBaseDataset(selectedVM) || ''; + } + void checkSourceEncryption(dataset); } - void checkSourceEncryption(dataset); - }); + ); + + let disableStopBeforeBackup = $state(false); + + watch( + () => form.mode, + (mode) => { + if (mode === 'dataset') { + form.stopBeforeBackup = false; + disableStopBeforeBackup = true; + } else { + disableStopBeforeBackup = false; + } + } + ); @@ -601,6 +632,10 @@ label="Stop before backup" bind:checked={form.stopBeforeBackup} classes="flex items-center gap-2" + disabled={disableStopBeforeBackup} + title={form.mode === 'dataset' + ? 'This option is only applicable for jail and VM backups' + : ''} /> @@ -637,19 +672,6 @@ will be backed up {/if} -
  • - Encryption: - - {#if srcEncryptionChecking} - ... - {:else if srcEncrypted} - - Enabled (keys auto-managed) - {:else} - None - {/if} - -
  • Schedule: {cronToHuman(form.cronExpr) || '(not set)'}{form.pruneTarget ? 'Enabled' : 'Disabled'}
  • -
  • - Stop before backup: - {form.stopBeforeBackup ? 'Enabled' : 'Disabled'} -
  • + + {#if !disableStopBeforeBackup} +
  • + Stop before backup: + {form.stopBeforeBackup ? 'Enabled' : 'Disabled'} +
  • + {/if} + + {#if form.mode !== 'jail' && form.mode !== 'vm'} + {#if srcEncryptionChecking} +
  • + +
  • + {:else if form.sourceDataset === ''} +
  • + + Select a dataset to view encryption information +
  • + {:else if srcEncrypted} +
  • + + Source is encrypted +
  • + {:else} +
  • + + Source is not encrypted +
  • + {/if} + {/if} diff --git a/web/src/locales/cs.po b/web/src/locales/cs.po index a4cd335a..8e7da6dd 100644 --- a/web/src/locales/cs.po +++ b/web/src/locales/cs.po @@ -13920,3 +13920,37 @@ msgstr "" #: src/lib/components/skeleton/BottomPanel.svelte msgid "Jail Template - Create" msgstr "" + +#~ msgid "Encryption: <0/>" +#~ msgstr "" + +#~ msgid "Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Encryption: <0>Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Checking source encryption..." +#~ msgstr "" + +#~ msgid "Source is encrypted (keys auto-managed)" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is not encrypted" +msgstr "" + +#~ msgid "No encryption information available" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Select a dataset to view encryption information" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "This option is only applicable for jail and VM backups" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is encrypted" +msgstr "" diff --git a/web/src/locales/de.po b/web/src/locales/de.po index 3608888d..ca6eebb9 100644 --- a/web/src/locales/de.po +++ b/web/src/locales/de.po @@ -13898,3 +13898,37 @@ msgstr "" #: src/lib/components/skeleton/BottomPanel.svelte msgid "Jail Template - Create" msgstr "" + +#~ msgid "Encryption: <0/>" +#~ msgstr "" + +#~ msgid "Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Encryption: <0>Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Checking source encryption..." +#~ msgstr "" + +#~ msgid "Source is encrypted (keys auto-managed)" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is not encrypted" +msgstr "" + +#~ msgid "No encryption information available" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Select a dataset to view encryption information" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "This option is only applicable for jail and VM backups" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is encrypted" +msgstr "" diff --git a/web/src/locales/en.po b/web/src/locales/en.po index 80c1b3e7..65d150c9 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -13921,3 +13921,37 @@ msgstr "VM Template - Create" #: src/lib/components/skeleton/BottomPanel.svelte msgid "Jail Template - Create" msgstr "Jail Template - Create" + +#~ msgid "Encryption: <0/>" +#~ msgstr "Encryption: <0/>" + +#~ msgid "Enabled (keys auto-managed)" +#~ msgstr "Enabled (keys auto-managed)" + +#~ msgid "Encryption: <0>Enabled (keys auto-managed)" +#~ msgstr "Encryption: <0>Enabled (keys auto-managed)" + +#~ msgid "Checking source encryption..." +#~ msgstr "Checking source encryption..." + +#~ msgid "Source is encrypted (keys auto-managed)" +#~ msgstr "Source is encrypted (keys auto-managed)" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is not encrypted" +msgstr "Source is not encrypted" + +#~ msgid "No encryption information available" +#~ msgstr "No encryption information available" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Select a dataset to view encryption information" +msgstr "Select a dataset to view encryption information" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "This option is only applicable for jail and VM backups" +msgstr "This option is only applicable for jail and VM backups" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is encrypted" +msgstr "Source is encrypted" diff --git a/web/src/locales/hi.po b/web/src/locales/hi.po index 24237795..f2ca367b 100644 --- a/web/src/locales/hi.po +++ b/web/src/locales/hi.po @@ -13898,3 +13898,37 @@ msgstr "" #: src/lib/components/skeleton/BottomPanel.svelte msgid "Jail Template - Create" msgstr "" + +#~ msgid "Encryption: <0/>" +#~ msgstr "" + +#~ msgid "Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Encryption: <0>Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Checking source encryption..." +#~ msgstr "" + +#~ msgid "Source is encrypted (keys auto-managed)" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is not encrypted" +msgstr "" + +#~ msgid "No encryption information available" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Select a dataset to view encryption information" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "This option is only applicable for jail and VM backups" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is encrypted" +msgstr "" diff --git a/web/src/locales/mal.po b/web/src/locales/mal.po index 5a7be31b..f57e00c0 100644 --- a/web/src/locales/mal.po +++ b/web/src/locales/mal.po @@ -13898,3 +13898,37 @@ msgstr "" #: src/lib/components/skeleton/BottomPanel.svelte msgid "Jail Template - Create" msgstr "" + +#~ msgid "Encryption: <0/>" +#~ msgstr "" + +#~ msgid "Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Encryption: <0>Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Checking source encryption..." +#~ msgstr "" + +#~ msgid "Source is encrypted (keys auto-managed)" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is not encrypted" +msgstr "" + +#~ msgid "No encryption information available" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Select a dataset to view encryption information" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "This option is only applicable for jail and VM backups" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is encrypted" +msgstr "" diff --git a/web/src/locales/zh-CN.po b/web/src/locales/zh-CN.po index ee8ae70b..3486afb9 100644 --- a/web/src/locales/zh-CN.po +++ b/web/src/locales/zh-CN.po @@ -13895,3 +13895,37 @@ msgstr "" #: src/lib/components/skeleton/BottomPanel.svelte msgid "Jail Template - Create" msgstr "" + +#~ msgid "Encryption: <0/>" +#~ msgstr "" + +#~ msgid "Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Encryption: <0>Enabled (keys auto-managed)" +#~ msgstr "" + +#~ msgid "Checking source encryption..." +#~ msgstr "" + +#~ msgid "Source is encrypted (keys auto-managed)" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is not encrypted" +msgstr "" + +#~ msgid "No encryption information available" +#~ msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Select a dataset to view encryption information" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "This option is only applicable for jail and VM backups" +msgstr "" + +#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte +msgid "Source is encrypted" +msgstr ""