mirror of
https://github.com/AlchemillaHQ/Sylve.git
synced 2026-06-14 00:46:34 +03:00
zfs: enc/restore: deal with children w/different keys
This commit is contained in:
@@ -11,7 +11,7 @@ ignore assets
|
||||
ignore internal/testutil
|
||||
|
||||
require (
|
||||
github.com/alchemillahq/gzfs v0.0.0-20260521021508-f16dc9b17ab5
|
||||
github.com/alchemillahq/gzfs v0.0.0-20260521085926-98d1cc21f2e9
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/beevik/etree v1.6.0
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1
|
||||
|
||||
@@ -4,10 +4,10 @@ github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/Sereal/Sereal/Go/sereal v0.0.0-20231009093132-b9187f1a92c6/go.mod h1:JwrycNnC8+sZPDyzM3MQ86LvaGzSpfxg885KOOwFRW4=
|
||||
github.com/alchemillahq/gzfs v0.0.0-20260409031910-c82074def96e h1:BCzTeVfyAsH7W4dp7s+rmxI8C+fcMZWtQxat5fiaFl0=
|
||||
github.com/alchemillahq/gzfs v0.0.0-20260409031910-c82074def96e/go.mod h1:uml1U7jMNb/dV6NS3nl4QqW6Nv/CBUDdEdzhl0S/p4Y=
|
||||
github.com/alchemillahq/gzfs v0.0.0-20260521021508-f16dc9b17ab5 h1:eCJ+N/3yihBNrPkq2MfRiG72IZUc7qVp0Wf2enNMqjM=
|
||||
github.com/alchemillahq/gzfs v0.0.0-20260521021508-f16dc9b17ab5/go.mod h1:uml1U7jMNb/dV6NS3nl4QqW6Nv/CBUDdEdzhl0S/p4Y=
|
||||
github.com/alchemillahq/gzfs v0.0.0-20260521085926-98d1cc21f2e9 h1:hRoVcJxJx+rmSOGGv8yk43X+xYqJHbMy4oQrvH6x19M=
|
||||
github.com/alchemillahq/gzfs v0.0.0-20260521085926-98d1cc21f2e9/go.mod h1:uml1U7jMNb/dV6NS3nl4QqW6Nv/CBUDdEdzhl0S/p4Y=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
|
||||
@@ -137,7 +137,7 @@ func (s *Service) ensureEncryptionKeyForDataset(ctx context.Context, ds *gzfs.Da
|
||||
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 {
|
||||
if err := ds.LoadKey(ctx, true); err != nil {
|
||||
return false, fmt.Errorf("load_key_failed: %w", err)
|
||||
}
|
||||
return true, nil
|
||||
@@ -145,21 +145,19 @@ func (s *Service) ensureEncryptionKeyForDataset(ctx context.Context, ds *gzfs.Da
|
||||
|
||||
// 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.
|
||||
// key in the cluster store until one loads successfully.
|
||||
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")
|
||||
Msg("prompt_keylocation_cannot_list_keys")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
if err := ds.LoadKeyWithPassphrase(ctx, key.KeyData); err != nil {
|
||||
if err := ds.LoadKeyWithPassphrase(ctx, key.KeyData, true); 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")
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -345,43 +346,88 @@ func (s *Service) runRestoreVMJob(
|
||||
// fixRestoredProperties corrects ZFS properties after a restore so the dataset
|
||||
// behaves like the original (not readonly, correct mountpoint, canmount=on).
|
||||
// For encrypted datasets, it ensures the key file is present and loads the key
|
||||
// before mounting.
|
||||
// before mounting. Walks all child datasets under the root to handle
|
||||
// independent encryption roots.
|
||||
func (s *Service) fixRestoredProperties(ctx context.Context, dataset string) {
|
||||
ds, err := s.getLocalDataset(ctx, dataset)
|
||||
dataset = normalizeDatasetPath(dataset)
|
||||
if dataset == "" {
|
||||
return
|
||||
}
|
||||
|
||||
allDatasets, err := s.listLocalFilesystemDatasets(ctx)
|
||||
if err != nil {
|
||||
logger.L.Warn().Err(err).Str("dataset", dataset).Msg("fix_restored_property_failed")
|
||||
return
|
||||
}
|
||||
if ds == nil {
|
||||
logger.L.Warn().Str("dataset", dataset).Msg("fix_restored_property_failed_dataset_not_found")
|
||||
logger.L.Warn().Err(err).Msg("fix_restored_property_list_datasets_failed")
|
||||
return
|
||||
}
|
||||
|
||||
if ds.IsEncrypted() {
|
||||
keyLoaded, err := s.ensureEncryptionKeyForDataset(ctx, ds)
|
||||
seen := map[string]struct{}{dataset: {}}
|
||||
subtree := []string{dataset}
|
||||
prefix := dataset + "/"
|
||||
|
||||
for _, candidate := range allDatasets {
|
||||
ds := normalizeDatasetPath(candidate)
|
||||
if ds == "" || ds == dataset {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(ds, prefix) {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[ds]; ok {
|
||||
continue
|
||||
}
|
||||
seen[ds] = struct{}{}
|
||||
subtree = append(subtree, ds)
|
||||
}
|
||||
|
||||
sort.SliceStable(subtree, func(i, j int) bool {
|
||||
di := strings.Count(subtree[i], "/")
|
||||
dj := strings.Count(subtree[j], "/")
|
||||
if di == dj {
|
||||
return subtree[i] < subtree[j]
|
||||
}
|
||||
return di < dj
|
||||
})
|
||||
|
||||
for idx, dsName := range subtree {
|
||||
ds, err := s.getLocalDataset(ctx, dsName)
|
||||
if err != nil {
|
||||
logger.L.Error().Err(err).Str("dataset", dataset).Msg("fix_restored_property_key_load_failed")
|
||||
return
|
||||
logger.L.Warn().Err(err).Str("dataset", dsName).Msg("fix_restored_property_get_failed")
|
||||
continue
|
||||
}
|
||||
if !keyLoaded {
|
||||
logger.L.Warn().Str("dataset", dataset).Msg("fix_restored_property_key_not_auto_loaded")
|
||||
if ds == nil {
|
||||
logger.L.Warn().Str("dataset", dsName).Msg("fix_restored_property_dataset_not_found")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := ds.SetProperties(ctx, "readonly", "off", "canmount", "on"); err != nil {
|
||||
logger.L.Warn().Err(err).Str("dataset", dataset).Msg("fix_restored_property_set_failed")
|
||||
}
|
||||
if ds.IsEncrypted() {
|
||||
keyLoaded, err := s.ensureEncryptionKeyForDataset(ctx, ds)
|
||||
if err != nil {
|
||||
logger.L.Error().Err(err).Str("dataset", dsName).Msg("fix_restored_property_key_load_failed")
|
||||
continue
|
||||
}
|
||||
if !keyLoaded {
|
||||
logger.L.Warn().Str("dataset", dsName).Msg("fix_restored_property_key_not_auto_loaded")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := utils.RunCommandWithContext(ctx, "zfs", "inherit", "mountpoint", dataset); err != nil {
|
||||
logger.L.Warn().Err(err).Str("dataset", dataset).Msg("fix_restored_property_inherit_mountpoint_failed")
|
||||
}
|
||||
if err := ds.SetProperties(ctx, "readonly", "off", "canmount", "on"); err != nil {
|
||||
logger.L.Warn().Err(err).Str("dataset", dsName).Msg("fix_restored_property_set_failed")
|
||||
}
|
||||
|
||||
if err := ds.Mount(ctx, false); err != nil {
|
||||
lowerErr := strings.ToLower(strings.TrimSpace(err.Error()))
|
||||
if strings.Contains(lowerErr, "already mounted") {
|
||||
return
|
||||
if idx == 0 {
|
||||
if _, err := utils.RunCommandWithContext(ctx, "zfs", "inherit", "mountpoint", dsName); err != nil {
|
||||
logger.L.Warn().Err(err).Str("dataset", dsName).Msg("fix_restored_property_inherit_mountpoint_failed")
|
||||
}
|
||||
}
|
||||
|
||||
if err := ds.Mount(ctx, false); err != nil {
|
||||
lowerErr := strings.ToLower(strings.TrimSpace(err.Error()))
|
||||
if strings.Contains(lowerErr, "already mounted") {
|
||||
continue
|
||||
}
|
||||
logger.L.Warn().Err(err).Str("dataset", dsName).Msg("fix_restored_property_mount_failed")
|
||||
}
|
||||
logger.L.Warn().Err(err).Str("dataset", dataset).Msg("fix_restored_property_mount_failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13249,11 +13249,10 @@ msgstr ""
|
||||
msgid "Redirecting to Traffic Rules..."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/inactive-node/+page.svelte
|
||||
msgid ""
|
||||
"This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
"powered on."
|
||||
msgstr "Tento uzel nyní není dosažitelný. Zkontrolujte připojení k síti a to, zda je uzel zapnutý."
|
||||
#~ msgid ""
|
||||
#~ "This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
#~ "powered on."
|
||||
#~ msgstr "Tento uzel nyní není dosažitelný. Zkontrolujte připojení k síti a to, zda je uzel zapnutý."
|
||||
|
||||
#~ msgid "⌘⇧Q"
|
||||
#~ msgstr "⌘⇧Q"
|
||||
|
||||
@@ -13246,11 +13246,10 @@ msgstr ""
|
||||
msgid "Redirecting to Traffic Rules..."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/inactive-node/+page.svelte
|
||||
msgid ""
|
||||
"This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
"powered on."
|
||||
msgstr ""
|
||||
#~ msgid ""
|
||||
#~ "This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
#~ "powered on."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "⌘⇧Q"
|
||||
#~ msgstr ""
|
||||
|
||||
@@ -13246,13 +13246,12 @@ msgstr "Jail Template - {0}"
|
||||
msgid "Redirecting to Traffic Rules..."
|
||||
msgstr "Redirecting to Traffic Rules..."
|
||||
|
||||
#: src/routes/inactive-node/+page.svelte
|
||||
msgid ""
|
||||
"This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
"powered on."
|
||||
msgstr ""
|
||||
"This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
"powered on."
|
||||
#~ msgid ""
|
||||
#~ "This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
#~ "powered on."
|
||||
#~ msgstr ""
|
||||
#~ "This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
#~ "powered on."
|
||||
|
||||
#~ msgid "⌘⇧Q"
|
||||
#~ msgstr "⌘⇧Q"
|
||||
|
||||
@@ -13246,11 +13246,10 @@ msgstr ""
|
||||
msgid "Redirecting to Traffic Rules..."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/inactive-node/+page.svelte
|
||||
msgid ""
|
||||
"This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
"powered on."
|
||||
msgstr ""
|
||||
#~ msgid ""
|
||||
#~ "This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
#~ "powered on."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "⌘⇧Q"
|
||||
#~ msgstr ""
|
||||
|
||||
@@ -13246,11 +13246,10 @@ msgstr ""
|
||||
msgid "Redirecting to Traffic Rules..."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/inactive-node/+page.svelte
|
||||
msgid ""
|
||||
"This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
"powered on."
|
||||
msgstr ""
|
||||
#~ msgid ""
|
||||
#~ "This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
#~ "powered on."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "⌘⇧Q"
|
||||
#~ msgstr ""
|
||||
|
||||
@@ -13243,11 +13243,10 @@ msgstr "Jail 模板 - {0}"
|
||||
msgid "Redirecting to Traffic Rules..."
|
||||
msgstr "正在重定向到流量规则..."
|
||||
|
||||
#: src/routes/inactive-node/+page.svelte
|
||||
msgid ""
|
||||
"This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
"powered on."
|
||||
msgstr "目前无法连接到此节点。请检查网络连接并确认节点已开启电源。"
|
||||
#~ msgid ""
|
||||
#~ "This node can’t be reached right now. Check the network connection and confirm the node is\n"
|
||||
#~ "powered on."
|
||||
#~ msgstr "目前无法连接到此节点。请检查网络连接并确认节点已开启电源。"
|
||||
|
||||
#~ msgid "⌘⇧Q"
|
||||
#~ msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user