kairos-agent/internal/agent/reset.go
Itxaka db703db5e5
Try to fix hooks (#718)
* fix hooks


---------

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-03-24 16:05:39 +01:00

206 lines
5.4 KiB
Go

package agent
import (
"encoding/json"
"fmt"
"github.com/kairos-io/kairos-agent/v2/pkg/uki"
internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
"strings"
"sync"
"time"
hook "github.com/kairos-io/kairos-agent/v2/internal/agent/hooks"
"github.com/kairos-io/kairos-agent/v2/internal/bus"
"github.com/kairos-io/kairos-agent/v2/internal/cmd"
"github.com/kairos-io/kairos-agent/v2/pkg/action"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
sdk "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/collector"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
"github.com/mudler/go-pluggable"
)
func Reset(reboot, unattended, resetOem bool, dir ...string) error {
// In both cases we want
if internalutils.UkiBootMode() == internalutils.UkiHDD {
return resetUki(reboot, unattended, resetOem, dir...)
} else if internalutils.UkiBootMode() == internalutils.UkiRemovableMedia {
return fmt.Errorf("reset is not supported on removable media, please run reset from the installed system recovery entry")
} else {
return reset(reboot, unattended, resetOem, dir...)
}
}
func reset(reboot, unattended, resetOem bool, dir ...string) error {
cfg, err := sharedReset(reboot, unattended, resetOem, dir...)
if err != nil {
return err
}
err = cfg.CheckForUsers()
if err != nil {
return err
}
// Load the installation Config from the cloud-config data
resetSpec, err := config.ReadResetSpecFromConfig(cfg)
if err != nil {
return err
}
err = resetSpec.Sanitize()
if err != nil {
return err
}
resetAction := action.NewResetAction(cfg, resetSpec)
if err = resetAction.Run(); err != nil {
cfg.Logger.Errorf("failed to reset: %s", err)
return err
}
bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
return hook.Run(*cfg, resetSpec, hook.FinishReset...)
}
func resetUki(reboot, unattended, resetOem bool, dir ...string) error {
cfg, err := sharedReset(reboot, unattended, resetOem, dir...)
if err != nil {
return err
}
err = cfg.CheckForUsers()
if err != nil {
return err
}
// Load the installation Config from the cloud-config data
resetSpec, err := config.ReadUkiResetSpecFromConfig(cfg)
if err != nil {
return err
}
err = resetSpec.Sanitize()
if err != nil {
return err
}
resetAction := uki.NewResetAction(cfg, resetSpec)
if err = resetAction.Run(); err != nil {
cfg.Logger.Errorf("failed to reset uki: %s", err)
return err
}
bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
return hook.Run(*cfg, resetSpec, hook.FinishReset...)
}
// sharedReset is the common reset code for both uki and non-uki
// sets the config, runs the event handler, publish the envent and gets the config
func sharedReset(reboot, unattended, resetOem bool, dir ...string) (c *config.Config, err error) {
bus.Manager.Initialize()
var optionsFromEvent map[string]string
// This config is only for reset branding.
agentConfig, err := LoadConfig()
if err != nil {
return c, err
}
if !unattended {
cmd.PrintBranding(DefaultBanner)
cmd.PrintText(agentConfig.Branding.Reset, "Reset")
// We don't close the lock, as none of the following actions are expected to return
lock := sync.Mutex{}
go func() {
// Wait for user input and go back to shell
utils.Prompt("") //nolint:errcheck
// give tty1 back
svc, err := machine.Getty(1)
if err == nil {
svc.Start() //nolint:errcheck
}
lock.Lock()
fmt.Println("Reset aborted")
panic(utils.Shell().Run())
}()
if !agentConfig.Fast {
time.Sleep(60 * time.Second)
}
lock.Lock()
}
ensureDataSourceReady()
// This gets the options from an event that can be sent by anyone.
// This should override the default config as it's much more dynamic
bus.Manager.Response(sdk.EventBeforeReset, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
err := json.Unmarshal([]byte(r.Data), &optionsFromEvent)
if err != nil {
fmt.Println(err)
}
})
bus.Manager.Publish(sdk.EventBeforeReset, sdk.EventPayload{}) //nolint:errcheck
// Prepare a config from the cli flags
r := ExtraConfigReset{}
r.Reset.ResetOem = resetOem
if resetOem {
r.Reset.ResetOem = true
}
if reboot {
r.Reset.Reboot = true
}
// Override the config with the event options
// Go over the possible options sent via event
if len(optionsFromEvent) > 0 {
if p := optionsFromEvent["reset-persistent"]; p != "" {
r.Reset.ResetPersistent = p == "true"
}
if o := optionsFromEvent["reset-oem"]; o != "" {
r.Reset.ResetOem = o == "true"
}
}
d, err := json.Marshal(r)
if err != nil {
c.Logger.Errorf("failed to marshal reset cmdline flags/event options: %s", err)
return c, err
}
cliConf := string(d)
// cliconf goes last so it can override the rest of the config files
c, err = config.Scan(collector.Directories(dir...), collector.Readers(strings.NewReader(cliConf)))
if err != nil {
return c, err
}
// Set strict validation from the event
if len(optionsFromEvent) > 0 {
if s := optionsFromEvent["strict"]; s != "" {
c.Strict = s == "true"
}
}
utils.SetEnv(c.Env)
return c, nil
}
// ExtraConfigReset is the struct that holds the reset options that come from flags and events
type ExtraConfigReset struct {
Reset struct {
ResetOem bool `json:"reset-oem,omitempty"`
ResetPersistent bool `json:"reset-persistent,omitempty"`
Reboot bool `json:"reboot,omitempty"`
} `json:"reset"`
}