2022-08-10 16:55:20 +00:00
|
|
|
package provider
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2024-07-15 09:26:04 +00:00
|
|
|
logging "github.com/ipfs/go-log/v2"
|
2022-08-10 16:55:20 +00:00
|
|
|
edgeVPNClient "github.com/mudler/edgevpn/api/client"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2023-03-31 12:31:54 +00:00
|
|
|
"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/utils"
|
2023-07-03 19:07:41 +00:00
|
|
|
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"
|
2022-12-01 16:44:19 +00:00
|
|
|
|
2023-07-03 19:07:41 +00:00
|
|
|
"github.com/kairos-io/provider-kairos/v2/internal/services"
|
2022-08-10 16:55:20 +00:00
|
|
|
|
2023-07-10 16:10:33 +00:00
|
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
2022-08-10 16:55:20 +00:00
|
|
|
"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{}
|
|
|
|
providerConfig := &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, providerConfig)
|
|
|
|
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
|
|
|
|
|
2022-12-12 12:44:46 +00:00
|
|
|
p2pBlockDefined := providerConfig.P2P != nil
|
|
|
|
tokenNotDefined := ((p2pBlockDefined && providerConfig.P2P.NetworkToken == "") || !p2pBlockDefined)
|
2022-12-12 14:38:21 +00:00
|
|
|
skipAuto := (p2pBlockDefined && !providerConfig.P2P.Auto.IsEnabled())
|
2022-08-10 16:55:20 +00:00
|
|
|
|
2022-12-09 15:00:28 +00:00
|
|
|
if providerConfig.P2P == nil && !providerConfig.K3s.Enabled && !providerConfig.K3sAgent.Enabled {
|
2022-11-27 13:42:16 +00:00
|
|
|
return pluggable.EventResponse{State: fmt.Sprintf("no kairos or k3s configuration. nothing to do: %s", cfg.Config)}
|
2022-08-10 16:55:20 +00:00
|
|
|
}
|
|
|
|
|
2023-09-08 12:40:11 +00:00
|
|
|
utils.SH("kairos-agent run-stage kairos-agent.bootstrap") //nolint:errcheck
|
2023-03-31 12:31:54 +00:00
|
|
|
bus.RunHookScript("/usr/bin/kairos-agent.bootstrap.hook") //nolint:errcheck
|
2022-08-10 16:55:20 +00:00
|
|
|
|
|
|
|
logLevel := "debug"
|
|
|
|
|
2022-12-12 12:44:46 +00:00
|
|
|
if p2pBlockDefined && providerConfig.P2P.LogLevel != "" {
|
2022-12-09 15:00:28 +00:00
|
|
|
logLevel = providerConfig.P2P.LogLevel
|
2022-08-10 16:55:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lvl, err := logging.LevelFromString(logLevel)
|
|
|
|
if err != nil {
|
|
|
|
return ErrorEvent("Failed setup logger: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Fixup Logging to file
|
|
|
|
loggerCfg := zap.NewProductionConfig()
|
|
|
|
loggerCfg.OutputPaths = []string{
|
|
|
|
cfg.Logfile,
|
|
|
|
}
|
|
|
|
logger, err := loggerCfg.Build()
|
|
|
|
if err != nil {
|
|
|
|
return ErrorEvent("Failed setup logger: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.SetAllLoggers(lvl)
|
|
|
|
|
|
|
|
log := &logging.ZapEventLogger{SugaredLogger: *logger.Sugar()}
|
|
|
|
|
|
|
|
// Do onetimebootstrap if K3s or K3s-agent are enabled.
|
2022-09-16 15:42:45 +00:00
|
|
|
// Those blocks are not required to be enabled in case of a kairos
|
2022-08-10 16:55:20 +00:00
|
|
|
// full automated setup. Otherwise, they must be explicitly enabled.
|
2022-12-07 10:35:17 +00:00
|
|
|
if (tokenNotDefined && (providerConfig.K3s.Enabled || providerConfig.K3sAgent.Enabled)) || skipAuto {
|
2022-08-10 16:55:20 +00:00
|
|
|
err := oneTimeBootstrap(log, providerConfig, func() error {
|
2022-08-12 07:51:59 +00:00
|
|
|
return SetupVPN(services.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, providerConfig)
|
2022-08-10 16:55:20 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return ErrorEvent("Failed setup: %s", err.Error())
|
|
|
|
}
|
|
|
|
return pluggable.EventResponse{}
|
2022-12-08 09:54:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if tokenNotDefined {
|
2022-12-07 10:35:17 +00:00
|
|
|
return ErrorEvent("No network token provided, or `k3s` block configured. Exiting")
|
2022-08-10 16:55:20 +00:00
|
|
|
}
|
|
|
|
|
2022-12-07 12:57:16 +00:00
|
|
|
// We might still want a VPN, but not to route traffic into
|
2022-12-09 15:00:28 +00:00
|
|
|
if providerConfig.P2P.VPNNeedsCreation() {
|
2022-12-01 17:14:05 +00:00
|
|
|
logger.Info("Configuring VPN")
|
|
|
|
if err := SetupVPN(services.EdgeVPNDefaultInstance, cfg.APIAddress, "/", true, providerConfig); err != nil {
|
|
|
|
return ErrorEvent("Failed setup VPN: %s", err.Error())
|
|
|
|
}
|
2022-12-09 15:00:28 +00:00
|
|
|
} else { // We need at least the API to co-ordinate
|
2022-12-01 17:14:05 +00:00
|
|
|
logger.Info("Configuring API")
|
|
|
|
if err := SetupAPI(cfg.APIAddress, "/", true, providerConfig); err != nil {
|
|
|
|
return ErrorEvent("Failed setup VPN: %s", err.Error())
|
|
|
|
}
|
2022-08-10 16:55:20 +00:00
|
|
|
}
|
|
|
|
|
2022-09-16 15:42:45 +00:00
|
|
|
networkID := "kairos"
|
2022-08-10 16:55:20 +00:00
|
|
|
|
2022-12-12 12:44:46 +00:00
|
|
|
if p2pBlockDefined && providerConfig.P2P.NetworkID != "" {
|
2022-12-09 15:00:28 +00:00
|
|
|
networkID = providerConfig.P2P.NetworkID
|
2022-08-10 16:55:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cc := service.NewClient(
|
|
|
|
networkID,
|
|
|
|
edgeVPNClient.NewClient(edgeVPNClient.WithHost(cfg.APIAddress)))
|
|
|
|
|
|
|
|
nodeOpts := []service.Option{
|
2022-12-27 19:27:11 +00:00
|
|
|
service.WithMinNodes(providerConfig.P2P.MinimumNodes),
|
2022-08-10 16:55:20 +00:00
|
|
|
service.WithLogger(log),
|
|
|
|
service.WithClient(cc),
|
|
|
|
service.WithUUID(machine.UUID()),
|
2022-09-16 15:42:45 +00:00
|
|
|
service.WithStateDir("/usr/local/.kairos/state"),
|
2022-12-09 15:00:28 +00:00
|
|
|
service.WithNetworkToken(providerConfig.P2P.NetworkToken),
|
2022-08-10 16:55:20 +00:00
|
|
|
service.WithPersistentRoles("auto"),
|
|
|
|
service.WithRoles(
|
|
|
|
service.RoleKey{
|
|
|
|
Role: "master",
|
2022-12-07 13:32:46 +00:00
|
|
|
RoleHandler: p2p.Master(c, providerConfig, false, false, "master"),
|
2022-12-06 16:27:29 +00:00
|
|
|
},
|
|
|
|
service.RoleKey{
|
|
|
|
Role: "master/clusterinit",
|
2022-12-07 13:32:46 +00:00
|
|
|
RoleHandler: p2p.Master(c, providerConfig, true, true, "master/clusterinit"),
|
2022-12-06 16:27:29 +00:00
|
|
|
},
|
|
|
|
service.RoleKey{
|
|
|
|
Role: "master/ha",
|
2022-12-07 13:32:46 +00:00
|
|
|
RoleHandler: p2p.Master(c, providerConfig, false, true, "master/ha"),
|
2022-08-10 16:55:20 +00:00
|
|
|
},
|
|
|
|
service.RoleKey{
|
|
|
|
Role: "worker",
|
2022-12-01 17:14:05 +00:00
|
|
|
RoleHandler: p2p.Worker(c, providerConfig),
|
2022-08-10 16:55:20 +00:00
|
|
|
},
|
|
|
|
service.RoleKey{
|
|
|
|
Role: "auto",
|
|
|
|
RoleHandler: role.Auto(c, providerConfig),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Optionally set up a specific node role if the user has defined so
|
2022-12-09 15:00:28 +00:00
|
|
|
if providerConfig.P2P.Role != "" {
|
|
|
|
nodeOpts = append(nodeOpts, service.WithDefaultRoles(providerConfig.P2P.Role))
|
2022-08-10 16:55:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 logging.StandardLogger, c *providerConfig.Config, vpnSetupFN func() error) error {
|
2023-11-17 17:40:31 +00:00
|
|
|
var err error
|
2022-08-10 16:55:20 +00:00
|
|
|
if role.SentinelExist() {
|
|
|
|
l.Info("Sentinel exists, nothing to do. exiting.")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
l.Info("One time bootstrap starting")
|
|
|
|
|
|
|
|
var svc machine.Service
|
|
|
|
k3sConfig := providerConfig.K3s{}
|
|
|
|
svcName := "k3s"
|
|
|
|
svcRole := "server"
|
|
|
|
|
|
|
|
if c.K3s.Enabled {
|
|
|
|
k3sConfig = c.K3s
|
|
|
|
} else if c.K3sAgent.Enabled {
|
|
|
|
k3sConfig = c.K3sAgent
|
|
|
|
svcName = "k3s-agent"
|
|
|
|
svcRole = "agent"
|
|
|
|
}
|
|
|
|
|
|
|
|
if utils.IsOpenRCBased() {
|
2023-11-17 17:40:31 +00:00
|
|
|
svc, err = openrc.NewService(
|
2022-08-10 16:55:20 +00:00
|
|
|
openrc.WithName(svcName),
|
|
|
|
)
|
|
|
|
} else {
|
2023-11-17 17:40:31 +00:00
|
|
|
svc, err = systemd.NewService(
|
2022-08-10 16:55:20 +00:00
|
|
|
systemd.WithName(svcName),
|
|
|
|
)
|
|
|
|
}
|
2024-01-10 16:11:36 +00:00
|
|
|
if err != nil {
|
|
|
|
l.Errorf("Failed to instanitate service: %s", err.Error())
|
|
|
|
return err
|
|
|
|
}
|
2022-08-10 16:55:20 +00:00
|
|
|
if svc == nil {
|
|
|
|
return fmt.Errorf("could not detect OS")
|
|
|
|
}
|
|
|
|
|
2024-01-10 16:11:36 +00:00
|
|
|
// Setup k3s service env file
|
|
|
|
envFile := machine.K3sEnvUnit(svcName)
|
2022-08-10 16:55:20 +00:00
|
|
|
if err := utils.WriteEnv(envFile,
|
|
|
|
k3sConfig.Env,
|
|
|
|
); err != nil {
|
2023-11-17 17:40:31 +00:00
|
|
|
l.Errorf("Failed to write env file: %s", err.Error())
|
2022-08-10 16:55:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
k3sbin := utils.K3sBin()
|
|
|
|
if k3sbin == "" {
|
2023-11-17 17:40:31 +00:00
|
|
|
l.Errorf("no k3s binary found (?)")
|
2022-08-10 16:55:20 +00:00
|
|
|
return fmt.Errorf("no k3s binary found (?)")
|
|
|
|
}
|
2024-01-10 16:11:36 +00:00
|
|
|
|
2022-08-10 16:55:20 +00:00
|
|
|
if err := svc.OverrideCmd(fmt.Sprintf("%s %s %s", k3sbin, svcRole, strings.Join(k3sConfig.Args, " "))); err != nil {
|
2023-11-17 17:40:31 +00:00
|
|
|
l.Errorf("Failed to override k3s command: %s", err.Error())
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-08-10 16:55:20 +00:00
|
|
|
if err := svc.Start(); err != nil {
|
2023-11-17 17:40:31 +00:00
|
|
|
l.Errorf("Failed to start service: %s", err.Error())
|
2022-08-10 16:55:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-01-10 16:11:36 +00:00
|
|
|
// NOTE: When this fails, it doesn't produce an error!
|
2022-08-10 16:55:20 +00:00
|
|
|
if err := svc.Enable(); err != nil {
|
2023-11-17 17:40:31 +00:00
|
|
|
l.Errorf("Failed to enable service: %s", err.Error())
|
2022-08-10 16:55:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-12-22 08:19:24 +00:00
|
|
|
if c.P2P != nil && c.P2P.VPNNeedsCreation() {
|
2022-08-10 16:55:20 +00:00
|
|
|
if err := vpnSetupFN(); err != nil {
|
2023-11-17 17:40:31 +00:00
|
|
|
l.Errorf("Failed to setup VPN: %s", err.Error())
|
2022-08-10 16:55:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return role.CreateSentinel()
|
|
|
|
}
|