basic: init: allow no-pool setup, zfs: pool: create base fs on creation

This commit is contained in:
hayzam
2026-03-12 16:08:25 +05:30
parent 483f0528de
commit c9ba46f453
5 changed files with 101 additions and 45 deletions
+1 -1
View File
@@ -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"`
}
+7 -28
View File
@@ -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
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: BSD-2-Clause
//
// Copyright (c) 2025 The FreeBSD Foundation.
//
// This software was developed by Hayzam Sherif <hayzam@alchemilla.io>
// of Alchemilla Ventures Pvt. Ltd. <hello@alchemilla.io>,
// 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
}
+12 -15
View File
@@ -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)
}
}
+33 -1
View File
@@ -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()