mirror of
https://github.com/kairos-io/provider-kairos.git
synced 2025-08-18 15:27:54 +00:00
265 lines
7.5 KiB
Go
265 lines
7.5 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/kairos-io/kairos-sdk/bus"
|
|
"github.com/kairos-io/kairos-sdk/machine"
|
|
"github.com/kairos-io/kairos-sdk/machine/openrc"
|
|
"github.com/kairos-io/kairos-sdk/machine/systemd"
|
|
"github.com/kairos-io/kairos-sdk/types"
|
|
"github.com/kairos-io/kairos-sdk/utils"
|
|
providerConfig "github.com/kairos-io/provider-kairos/v2/internal/provider/config"
|
|
"github.com/kairos-io/provider-kairos/v2/internal/role"
|
|
p2p "github.com/kairos-io/provider-kairos/v2/internal/role/p2p"
|
|
edgeVPNClient "github.com/mudler/edgevpn/api/client"
|
|
|
|
"github.com/kairos-io/provider-kairos/v2/internal/services"
|
|
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
|
"github.com/mudler/edgevpn/api/client/service"
|
|
"github.com/mudler/go-pluggable"
|
|
)
|
|
|
|
func Bootstrap(e *pluggable.Event) pluggable.EventResponse {
|
|
cfg := &bus.BootstrapPayload{}
|
|
err := json.Unmarshal([]byte(e.Data), cfg)
|
|
if err != nil {
|
|
return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), e.Data)
|
|
}
|
|
|
|
c := &config.Config{}
|
|
prvConfig := &providerConfig.Config{}
|
|
err = config.FromString(cfg.Config, c)
|
|
if err != nil {
|
|
return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), cfg.Config)
|
|
}
|
|
|
|
err = config.FromString(cfg.Config, prvConfig)
|
|
if err != nil {
|
|
return ErrorEvent("Failed reading JSON input: %s input '%s'", err.Error(), cfg.Config)
|
|
}
|
|
// TODO: this belong to a systemd service that is started instead
|
|
|
|
p2pBlockDefined := prvConfig.P2P != nil
|
|
tokenNotDefined := (p2pBlockDefined && prvConfig.P2P.NetworkToken == "") || !p2pBlockDefined
|
|
skipAuto := p2pBlockDefined && !prvConfig.P2P.Auto.IsEnabled()
|
|
|
|
if prvConfig.P2P == nil && !prvConfig.IsAKubernetesDistributionEnabled() {
|
|
return pluggable.EventResponse{State: fmt.Sprintf("no kubernetes distribution configuration. nothing to do: %s", cfg.Config)}
|
|
}
|
|
|
|
utils.SH("kairos-agent run-stage kairos-agent.bootstrap") //nolint:errcheck
|
|
bus.RunHookScript("/usr/bin/kairos-agent.bootstrap.hook") //nolint:errcheck
|
|
|
|
logLevel := "debug"
|
|
|
|
if p2pBlockDefined && prvConfig.P2P.LogLevel != "" {
|
|
logLevel = prvConfig.P2P.LogLevel
|
|
}
|
|
|
|
logger := types.NewKairosLogger("provider", logLevel, false)
|
|
|
|
// Do onetimebootstrap if a Kubernetes distribution is enabled.
|
|
// Those blocks are not required to be enabled in case of a kairos
|
|
// full automated setup. Otherwise, they must be explicitly enabled.
|
|
if (tokenNotDefined && prvConfig.IsAKubernetesDistributionEnabled()) || skipAuto {
|
|
err := oneTimeBootstrap(logger, prvConfig, func() error {
|
|
return SetupVPN(services.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, prvConfig)
|
|
})
|
|
if err != nil {
|
|
return ErrorEvent("Failed setup: %s", err.Error())
|
|
}
|
|
return pluggable.EventResponse{}
|
|
}
|
|
|
|
if tokenNotDefined {
|
|
return ErrorEvent("No network token provided, or kubernetes distribution (k3s, k0s) block configured. Exiting")
|
|
}
|
|
|
|
// We might still want a VPN, but not to route traffic into
|
|
if prvConfig.P2P.VPNNeedsCreation() {
|
|
logger.Info("Configuring VPN")
|
|
if err := SetupVPN(services.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, prvConfig); err != nil {
|
|
return ErrorEvent("Failed setup VPN: %s", err.Error())
|
|
}
|
|
} else { // We need at least the API to co-ordinate
|
|
logger.Info("Configuring API")
|
|
if err := SetupAPI(cfg.APIAddress, "/", true, prvConfig); err != nil {
|
|
return ErrorEvent("Failed setup VPN: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
networkID := "kairos"
|
|
|
|
if p2pBlockDefined && prvConfig.P2P.NetworkID != "" {
|
|
networkID = prvConfig.P2P.NetworkID
|
|
}
|
|
|
|
cc := service.NewClient(
|
|
networkID,
|
|
edgeVPNClient.NewClient(edgeVPNClient.WithHost(cfg.APIAddress)))
|
|
|
|
nodeOpts := []service.Option{
|
|
service.WithMinNodes(prvConfig.P2P.MinimumNodes),
|
|
service.WithLogger(logger),
|
|
service.WithClient(cc),
|
|
service.WithUUID(machine.UUID()),
|
|
service.WithStateDir("/usr/local/.kairos/state"),
|
|
service.WithNetworkToken(prvConfig.P2P.NetworkToken),
|
|
service.WithPersistentRoles("auto"),
|
|
service.WithRoles(
|
|
service.RoleKey{
|
|
Role: "master",
|
|
RoleHandler: p2p.Master(c, prvConfig, false, false, "master"),
|
|
},
|
|
service.RoleKey{
|
|
Role: "master/clusterinit",
|
|
RoleHandler: p2p.Master(c, prvConfig, true, true, "master/clusterinit"),
|
|
},
|
|
service.RoleKey{
|
|
Role: "master/ha",
|
|
RoleHandler: p2p.Master(c, prvConfig, false, true, "master/ha"),
|
|
},
|
|
service.RoleKey{
|
|
Role: "worker",
|
|
RoleHandler: p2p.Worker(c, prvConfig),
|
|
},
|
|
service.RoleKey{
|
|
Role: "auto",
|
|
RoleHandler: role.Auto(c, prvConfig),
|
|
},
|
|
),
|
|
}
|
|
|
|
// Optionally set up a specific node role if the user has defined so
|
|
if prvConfig.P2P.Role != "" {
|
|
nodeOpts = append(nodeOpts, service.WithDefaultRoles(prvConfig.P2P.Role))
|
|
}
|
|
|
|
k, err := service.NewNode(nodeOpts...)
|
|
if err != nil {
|
|
return ErrorEvent("Failed creating node: %s", err.Error())
|
|
}
|
|
err = k.Start(context.Background())
|
|
if err != nil {
|
|
return ErrorEvent("Failed start: %s", err.Error())
|
|
}
|
|
|
|
return pluggable.EventResponse{
|
|
State: "",
|
|
Data: "",
|
|
Error: "shouldn't return here",
|
|
}
|
|
}
|
|
|
|
func oneTimeBootstrap(l types.KairosLogger, c *providerConfig.Config, vpnSetupFN func() error) error {
|
|
var err error
|
|
if role.SentinelExist() {
|
|
l.Info("Sentinel exists, nothing to do. exiting.")
|
|
return nil
|
|
}
|
|
l.Info("One time bootstrap starting")
|
|
|
|
var svc machine.Service
|
|
var svcName, svcRole, envFile, binPath, args string
|
|
var svcEnv map[string]string
|
|
|
|
if !c.IsAKubernetesDistributionEnabled() {
|
|
l.Info("No Kubernetes configuration found, skipping bootstrap.")
|
|
return nil
|
|
}
|
|
|
|
if c.IsK3sAgentEnabled() {
|
|
svcName = "k3s-agent"
|
|
svcRole = "agent"
|
|
svcEnv = c.K3sAgent.Env
|
|
args = strings.Join(c.K3sAgent.Args, " ")
|
|
}
|
|
|
|
if c.IsK3sEnabled() {
|
|
svcName = "k3s"
|
|
svcRole = "server"
|
|
svcEnv = c.K3s.Env
|
|
args = strings.Join(c.K3s.Args, " ")
|
|
}
|
|
|
|
if c.IsK0sEnabled() {
|
|
svcName = "k0scontroller"
|
|
svcRole = "controller"
|
|
svcEnv = c.K0s.Env
|
|
args = strings.Join(c.K0s.Args, " ")
|
|
}
|
|
|
|
if c.IsK0sWorkerEnabled() {
|
|
svcName = "k0sworker"
|
|
svcRole = "worker"
|
|
svcEnv = c.K0sWorker.Env
|
|
args = strings.Join(c.K0sWorker.Args, " ")
|
|
}
|
|
|
|
if c.IsK3sDistributionEnabled() {
|
|
envFile = machine.K3sEnvUnit(svcName)
|
|
binPath = utils.K3sBin()
|
|
}
|
|
|
|
if c.IsK0sDistributionEnabled() {
|
|
envFile = machine.K0sEnvUnit(svcName)
|
|
binPath = utils.K0sBin()
|
|
}
|
|
|
|
if binPath == "" {
|
|
l.Errorf("no %s binary fouund", svcName)
|
|
return fmt.Errorf("no %s binary found", svcName)
|
|
}
|
|
|
|
if err := utils.WriteEnv(envFile, svcEnv); err != nil {
|
|
l.Errorf("Failed to write %s env file: %s", svcName, err.Error())
|
|
return err
|
|
}
|
|
|
|
// Initialize the service based on the system's init system
|
|
if utils.IsOpenRCBased() {
|
|
svc, err = openrc.NewService(openrc.WithName(svcName))
|
|
} else {
|
|
svc, err = systemd.NewService(systemd.WithName(svcName))
|
|
}
|
|
|
|
if err != nil {
|
|
l.Errorf("Failed to instantiate service: %s", err.Error())
|
|
return err
|
|
}
|
|
if svc == nil {
|
|
return fmt.Errorf("could not detect OS")
|
|
}
|
|
|
|
// Override the service command and start it
|
|
if err := svc.OverrideCmd(fmt.Sprintf("%s %s %s", binPath, svcRole, args)); err != nil {
|
|
l.Errorf("Failed to override service command: %s", err.Error())
|
|
return err
|
|
}
|
|
if err := svc.Start(); err != nil {
|
|
l.Errorf("Failed to start service: %s", err.Error())
|
|
return err
|
|
}
|
|
|
|
// When this fails, it doesn't produce an error!
|
|
if err := svc.Enable(); err != nil {
|
|
l.Errorf("Failed to enable service: %s", err.Error())
|
|
return err
|
|
}
|
|
|
|
// Setup VPN if required
|
|
if c.P2P != nil && c.P2P.VPNNeedsCreation() {
|
|
if err := vpnSetupFN(); err != nil {
|
|
l.Errorf("Failed to setup VPN: %s", err.Error())
|
|
return err
|
|
}
|
|
}
|
|
|
|
return role.CreateSentinel()
|
|
}
|