incus-mirror/cmd/incus/admin_init_auto.go
Stéphane Graber 40dd4f151d
Rewrite Go import path to v7
Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
2026-05-02 01:18:25 +02:00

201 lines
5.9 KiB
Go

//go:build linux
package main
import (
"errors"
"fmt"
"slices"
incus "github.com/lxc/incus/v7/client"
"github.com/lxc/incus/v7/internal/i18n"
"github.com/lxc/incus/v7/internal/linux"
"github.com/lxc/incus/v7/internal/ports"
internalUtil "github.com/lxc/incus/v7/internal/util"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/util"
)
func (c *cmdAdminInit) runAuto(d incus.InstanceServer, server *api.Server) (*api.InitPreseed, error) {
// Quick checks.
if c.flagStorageBackend != "" && !slices.Contains([]string{"dir", "btrfs", "lvm", "zfs"}, c.flagStorageBackend) {
return nil, fmt.Errorf(i18n.G("The requested backend '%s' isn't supported by init"), c.flagStorageBackend)
}
if c.flagStorageBackend != "" && !slices.Contains(linux.AvailableStorageDrivers(internalUtil.VarPath(), server.Environment.StorageSupportedDrivers, internalUtil.PoolTypeAny), c.flagStorageBackend) {
return nil, fmt.Errorf(i18n.G("The requested backend '%s' isn't available on your system (missing tools)"), c.flagStorageBackend)
}
if c.flagStorageBackend == "dir" || c.flagStorageBackend == "" {
if c.flagStorageLoopSize != -1 || c.flagStorageDevice != "" || c.flagStoragePool != "" {
return nil, errors.New(i18n.G("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend"))
}
} else {
if c.flagStorageLoopSize != -1 && c.flagStorageDevice != "" {
return nil, errors.New(i18n.G("Only one of --storage-create-device or --storage-create-loop can be specified"))
}
}
if c.flagNetworkAddress == "" {
if c.flagNetworkPort != -1 {
return nil, errors.New(i18n.G("--network-port can't be used without --network-address"))
}
}
storagePools, err := d.GetStoragePoolNames()
if err != nil {
return nil, fmt.Errorf(i18n.G("Failed to retrieve list of storage pools: %w"), err)
}
if len(storagePools) > 0 && (c.flagStorageBackend != "" || c.flagStorageDevice != "" || c.flagStorageLoopSize != -1 || c.flagStoragePool != "") {
return nil, errors.New(i18n.G("Storage has already been configured"))
}
// Detect the backing filesystem.
backingFs, err := linux.DetectFilesystem(internalUtil.VarPath())
if err != nil {
backingFs = "dir"
}
// Get the possible local storage drivers.
storageDrivers := linux.AvailableStorageDrivers(internalUtil.VarPath(), server.Environment.StorageSupportedDrivers, internalUtil.PoolTypeLocal)
// Defaults
if c.flagNetworkPort == -1 {
c.flagNetworkPort = ports.HTTPSDefaultPort
}
if c.flagStorageBackend == "" && c.flagStoragePool == "" && backingFs == "btrfs" && slices.Contains(storageDrivers, "btrfs") {
// Use btrfs subvol if running on btrfs.
c.flagStoragePool = internalUtil.VarPath("storage-pools", "default")
c.flagStorageBackend = "btrfs"
} else if c.flagStorageBackend == "" {
c.flagStorageBackend = "dir"
}
// Fill in the node configuration
config := api.InitLocalPreseed{}
config.Config = map[string]string{}
// Network listening
if c.flagNetworkAddress != "" {
config.Config["core.https_address"] = internalUtil.CanonicalNetworkAddressFromAddressAndPort(c.flagNetworkAddress, c.flagNetworkPort, ports.HTTPSDefaultPort)
}
// Storage configuration
if len(storagePools) == 0 {
// Storage pool
pool := api.StoragePoolsPost{
Name: "default",
Driver: c.flagStorageBackend,
}
pool.Config = map[string]string{}
if c.flagStorageDevice != "" {
pool.Config["source"] = c.flagStorageDevice
} else if c.flagStorageLoopSize > 0 {
pool.Config["size"] = fmt.Sprintf("%dGiB", c.flagStorageLoopSize)
} else {
pool.Config["source"] = c.flagStoragePool
}
// If using a device or loop, --storage-pool refers to the name of the new pool
if c.flagStoragePool != "" && (c.flagStorageDevice != "" || c.flagStorageLoopSize != -1) {
pool.Name = c.flagStoragePool
}
config.StoragePools = []api.StoragePoolsPost{pool}
// Profile entry
config.Profiles = []api.InitProfileProjectPost{{
ProfilesPost: api.ProfilesPost{
Name: "default",
ProfilePut: api.ProfilePut{
Devices: map[string]map[string]string{
"root": {
"type": "disk",
"path": "/",
"pool": pool.Name,
},
},
},
},
Project: api.ProjectDefaultName,
}}
}
// Network configuration
networks, err := d.GetNetworks()
if err != nil {
return nil, fmt.Errorf(i18n.G("Failed to retrieve list of networks: %w"), err)
}
// Extract managed networks
managedNetworks := []api.Network{}
for _, network := range networks {
if network.Managed {
managedNetworks = append(managedNetworks, network)
}
}
// Look for an existing network device in the profile
defaultProfileNetwork := false
defaultProfile, _, err := d.GetProfile("default")
if err == nil {
for _, dev := range defaultProfile.Devices {
if dev["type"] == "nic" {
defaultProfileNetwork = true
break
}
}
}
// Define a new network
if len(managedNetworks) == 0 && !defaultProfileNetwork {
// Find a new name
idx := 0
for {
if util.PathExists(fmt.Sprintf("/sys/class/net/incusbr%d", idx)) {
idx++
continue
}
break
}
// Define the new network
network := api.InitNetworksProjectPost{}
network.Name = fmt.Sprintf("incusbr%d", idx)
network.Project = api.ProjectDefaultName
config.Networks = append(config.Networks, network)
// Add it to the profile
if config.Profiles == nil {
config.Profiles = []api.InitProfileProjectPost{{
ProfilesPost: api.ProfilesPost{
Name: "default",
ProfilePut: api.ProfilePut{
Devices: map[string]map[string]string{
"eth0": {
"type": "nic",
"network": network.Name,
"name": "eth0",
},
},
},
},
Project: api.ProjectDefaultName,
}}
} else {
config.Profiles[0].Devices["eth0"] = map[string]string{
"type": "nic",
"network": network.Name,
"name": "eth0",
}
}
}
return &api.InitPreseed{InitLocalPreseed: config}, nil
}