From c9ba46f4533d30decc2fd2b67ea2ec679ed0ebe6 Mon Sep 17 00:00:00 2001 From: hayzam Date: Thu, 12 Mar 2026 16:08:25 +0530 Subject: [PATCH] basic: init: allow no-pool setup, zfs: pool: create base fs on creation --- internal/interfaces/services/system/basic.go | 2 +- internal/services/system/basic.go | 35 +++----------- internal/services/system/pool_bootstrap.go | 48 ++++++++++++++++++++ internal/services/system/settings.go | 27 +++++------ internal/services/zfs/pool.go | 34 +++++++++++++- 5 files changed, 101 insertions(+), 45 deletions(-) create mode 100644 internal/services/system/pool_bootstrap.go diff --git a/internal/interfaces/services/system/basic.go b/internal/interfaces/services/system/basic.go index 672d5ad9..d0ff6e92 100644 --- a/internal/interfaces/services/system/basic.go +++ b/internal/interfaces/services/system/basic.go @@ -11,6 +11,6 @@ package systemServiceInterfaces import "github.com/alchemillahq/sylve/internal/db/models" type InitializeRequest struct { - Pools []string `json:"pools" binding:"required"` + Pools []string `json:"pools"` Services []models.AvailableService `json:"services" binding:"required"` } diff --git a/internal/services/system/basic.go b/internal/services/system/basic.go index f0c4849c..5e444d12 100644 --- a/internal/services/system/basic.go +++ b/internal/services/system/basic.go @@ -12,7 +12,6 @@ import ( "context" "errors" "fmt" - "strings" "github.com/alchemillahq/gzfs" "github.com/alchemillahq/sylve/internal/db/models" @@ -56,10 +55,6 @@ func (s *Service) Initialize(ctx context.Context, req systemServiceInterfaces.In return []error{fmt.Errorf("system_already_initialized")} } - if len(req.Pools) == 0 { - return []error{fmt.Errorf("no_pools_provided")} - } - var newSets []*gzfs.Dataset for _, poolName := range req.Pools { @@ -72,32 +67,16 @@ func (s *Service) Initialize(ctx context.Context, req systemServiceInterfaces.In return []error{fmt.Errorf("pool_not_found_%s", poolName)} } - toCreate := []string{"sylve", "sylve/virtual-machines", "sylve/jails"} - for _, dataset := range toCreate { - fullDatasetName := fmt.Sprintf("%s/%s", pool.Name, dataset) - - sets, err := s.GZFS.ZFS.List(ctx, false, fullDatasetName) - if err != nil { - if !strings.Contains(err.Error(), "dataset does not exist") { - return []error{fmt.Errorf("error_checking_dataset_%s: %w", fullDatasetName, err)} - } + created, err := s.ensureSylveDatasetsOnPool(ctx, pool.Name) + if err != nil { + for i := len(newSets) - 1; i >= 0; i-- { + newSets[i].Destroy(ctx, true, false) } - exists := len(sets) > 0 - props := map[string]string{} - - if !exists { - created, err := s.GZFS.ZFS.CreateFilesystem(ctx, fullDatasetName, props) - if err != nil { - for i := len(newSets) - 1; i >= 0; i-- { - newSets[i].Destroy(ctx, true, false) - } - - return []error{fmt.Errorf("error_creating_dataset_%s: %w", fullDatasetName, err)} - } - newSets = append(newSets, created) - } + return []error{err} } + + newSets = append(newSets, created...) } var errs []error diff --git a/internal/services/system/pool_bootstrap.go b/internal/services/system/pool_bootstrap.go new file mode 100644 index 00000000..1c00bf4d --- /dev/null +++ b/internal/services/system/pool_bootstrap.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright (c) 2025 The FreeBSD Foundation. +// +// This software was developed by Hayzam Sherif +// of Alchemilla Ventures Pvt. Ltd. , +// under sponsorship from the FreeBSD Foundation. + +package system + +import ( + "context" + "fmt" + "strings" + + "github.com/alchemillahq/gzfs" +) + +var requiredSylveDatasets = []string{ + "sylve", + "sylve/virtual-machines", + "sylve/jails", +} + +func (s *Service) ensureSylveDatasetsOnPool(ctx context.Context, poolName string) ([]*gzfs.Dataset, error) { + var created []*gzfs.Dataset + + for _, dataset := range requiredSylveDatasets { + fullDatasetName := fmt.Sprintf("%s/%s", poolName, dataset) + found, err := s.GZFS.ZFS.Get(ctx, fullDatasetName, false) + if err != nil && !strings.Contains(strings.ToLower(err.Error()), "does not exist") { + return nil, fmt.Errorf("error_checking_dataset_%s: %w", fullDatasetName, err) + } + + if found != nil { + continue + } + + newDataset, err := s.GZFS.ZFS.CreateFilesystem(ctx, fullDatasetName, nil) + if err != nil { + return nil, fmt.Errorf("error_creating_dataset_%s: %w", fullDatasetName, err) + } + + created = append(created, newDataset) + } + + return created, nil +} diff --git a/internal/services/system/settings.go b/internal/services/system/settings.go index 2bb19f2b..8a00af55 100644 --- a/internal/services/system/settings.go +++ b/internal/services/system/settings.go @@ -13,7 +13,6 @@ import ( "fmt" "strings" - "github.com/alchemillahq/gzfs" "github.com/alchemillahq/sylve/internal/db/models" jailModels "github.com/alchemillahq/sylve/internal/db/models/jail" vmModels "github.com/alchemillahq/sylve/internal/db/models/vm" @@ -99,24 +98,22 @@ func (s *Service) AddUsablePools(ctx context.Context, pools []string) error { } } - toCreate := []string{"sylve", "sylve/virtual-machines", "sylve/jails"} - var newSets []*gzfs.Dataset + var newSets []string for _, poolName := range pools { - for _, dataset := range toCreate { - datasetPath := fmt.Sprintf("%s/%s", poolName, dataset) - _, err := s.GZFS.ZFS.Get(ctx, datasetPath, false) - if err != nil { - created, err := s.GZFS.ZFS.CreateFilesystem(ctx, datasetPath, nil) - if err != nil { - for i := len(newSets) - 1; i >= 0; i-- { - newSets[i].Destroy(ctx, true, false) - } - return fmt.Errorf("failed_to_create_dataset_%s: %w", datasetPath, err) + created, err := s.ensureSylveDatasetsOnPool(ctx, poolName) + if err != nil { + for i := len(newSets) - 1; i >= 0; i-- { + if ds, getErr := s.GZFS.ZFS.Get(ctx, newSets[i], false); getErr == nil && ds != nil { + _ = ds.Destroy(ctx, true, false) } - - newSets = append(newSets, created) } + + return err + } + + for _, ds := range created { + newSets = append(newSets, ds.Name) } } diff --git a/internal/services/zfs/pool.go b/internal/services/zfs/pool.go index 249cfa0d..4dbf5686 100644 --- a/internal/services/zfs/pool.go +++ b/internal/services/zfs/pool.go @@ -128,12 +128,18 @@ func (s *Service) CreatePool(ctx context.Context, req zfsServiceInterfaces.Creat return fmt.Errorf("zpool_create_failed: %v", err) } + if err := s.ensureSylveDatasetsOnPool(ctx, req.Name); err != nil { + return err + } + var basicSettings models.BasicSettings if err := s.DB.First(&basicSettings).Error; err != nil { return fmt.Errorf("failed_to_get_basic_settings: %v", err) } - basicSettings.Pools = append(basicSettings.Pools, req.Name) + if !slices.Contains(basicSettings.Pools, req.Name) { + basicSettings.Pools = append(basicSettings.Pools, req.Name) + } if err := s.DB.Save(&basicSettings).Error; err != nil { return fmt.Errorf("failed_to_update_basic_settings: %v", err) @@ -142,6 +148,32 @@ func (s *Service) CreatePool(ctx context.Context, req zfsServiceInterfaces.Creat return nil } +func (s *Service) ensureSylveDatasetsOnPool(ctx context.Context, poolName string) error { + requiredDatasets := []string{ + "sylve", + "sylve/virtual-machines", + "sylve/jails", + } + + for _, dataset := range requiredDatasets { + fullDatasetName := fmt.Sprintf("%s/%s", poolName, dataset) + found, err := s.GZFS.ZFS.Get(ctx, fullDatasetName, false) + if err != nil && !strings.Contains(strings.ToLower(err.Error()), "does not exist") { + return fmt.Errorf("failed_to_check_dataset_%s: %w", fullDatasetName, err) + } + + if found != nil { + continue + } + + if _, err := s.GZFS.ZFS.CreateFilesystem(ctx, fullDatasetName, nil); err != nil { + return fmt.Errorf("failed_to_create_dataset_%s: %w", fullDatasetName, err) + } + } + + return nil +} + func (s *Service) EditPool(ctx context.Context, name string, props map[string]string, spares []string) error { s.syncMutex.Lock() defer s.syncMutex.Unlock()