Writes bootcmd
This commit is contained in:
@@ -11,5 +11,7 @@ COPY out/fw_printenv ./
|
|||||||
COPY out/fw_setenv ./
|
COPY out/fw_setenv ./
|
||||||
COPY --from=cacerts /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=cacerts /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
|
||||||
|
ENV PATH=/
|
||||||
|
|
||||||
ENTRYPOINT ["/ctl"]
|
ENTRYPOINT ["/ctl"]
|
||||||
CMD ["agent"]
|
CMD ["agent"]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"example.com/monok8s/pkg/node"
|
"example.com/monok8s/pkg/node"
|
||||||
|
"example.com/monok8s/pkg/node/uboot"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
@@ -30,7 +31,8 @@ func NewRegistry(ctx *node.NodeContext) *Registry {
|
|||||||
"ConfigureDefaultCNI": node.ConfigureDefaultCNI,
|
"ConfigureDefaultCNI": node.ConfigureDefaultCNI,
|
||||||
"ConfigureHostname": node.ConfigureHostname(netCfg),
|
"ConfigureHostname": node.ConfigureHostname(netCfg),
|
||||||
"ConfigureMgmtInterface": node.ConfigureMgmtInterface(netCfg),
|
"ConfigureMgmtInterface": node.ConfigureMgmtInterface(netCfg),
|
||||||
"ConfigureUBootCommands": node.ConfigureUBootCommands,
|
"ConfigureABBoot": uboot.ConfigureABBoot,
|
||||||
|
"ConfigureUBootCommands": uboot.ConfigureUBootCommands,
|
||||||
"DetectLocalClusterState": node.DetectLocalClusterState,
|
"DetectLocalClusterState": node.DetectLocalClusterState,
|
||||||
"EnsureIPForward": node.EnsureIPForward,
|
"EnsureIPForward": node.EnsureIPForward,
|
||||||
"ReconcileControlPlane": node.ReconcileControlPlane,
|
"ReconcileControlPlane": node.ReconcileControlPlane,
|
||||||
|
|||||||
@@ -137,6 +137,11 @@ func NewRunner(cfg *monov1alpha1.MonoKSConfig) *Runner {
|
|||||||
Name: "Ensure fw_env config and u-boot-tools availablilty",
|
Name: "Ensure fw_env config and u-boot-tools availablilty",
|
||||||
Desc: "Install or generate /etc/fw_env.config for U-Boot environment access",
|
Desc: "Install or generate /etc/fw_env.config for U-Boot environment access",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
RegKey: "ConfigureABBoot",
|
||||||
|
Name: "Configure A/B booting environment",
|
||||||
|
Desc: "Make A/B booting possible",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
RegKey: "ApplyControlAgentDaemonSetResources",
|
RegKey: "ApplyControlAgentDaemonSetResources",
|
||||||
Name: "Apply daemonset for control agent",
|
Name: "Apply daemonset for control agent",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func newInternalFWPrintEnvCmd() *cobra.Command {
|
|||||||
|
|
||||||
res, err := runner.RunWithOptions(
|
res, err := runner.RunWithOptions(
|
||||||
ctx,
|
ctx,
|
||||||
"/fw_printenv",
|
"fw_printenv",
|
||||||
runArgs,
|
runArgs,
|
||||||
system.RunOptions{
|
system.RunOptions{
|
||||||
Quiet: true,
|
Quiet: true,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func newInternalFWSetEnvCmd() *cobra.Command {
|
|||||||
// Preflight first so failure is clearer than blindly writing.
|
// Preflight first so failure is clearer than blindly writing.
|
||||||
preflightRes, err := runner.RunWithOptions(
|
preflightRes, err := runner.RunWithOptions(
|
||||||
ctx,
|
ctx,
|
||||||
"/fw_printenv",
|
"fw_printenv",
|
||||||
[]string{"-c", configPath},
|
[]string{"-c", configPath},
|
||||||
system.RunOptions{
|
system.RunOptions{
|
||||||
Quiet: true,
|
Quiet: true,
|
||||||
@@ -70,7 +70,7 @@ func newInternalFWSetEnvCmd() *cobra.Command {
|
|||||||
|
|
||||||
res, err := runner.RunWithOptions(
|
res, err := runner.RunWithOptions(
|
||||||
ctx,
|
ctx,
|
||||||
"/fw_setenv",
|
"fw_setenv",
|
||||||
[]string{
|
[]string{
|
||||||
"-c", configPath,
|
"-c", configPath,
|
||||||
key, value,
|
key, value,
|
||||||
|
|||||||
30
clitools/pkg/node/uboot/ab.go
Normal file
30
clitools/pkg/node/uboot/ab.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package uboot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"example.com/monok8s/pkg/node"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConfigureABBoot(ctx context.Context, nctx *node.NodeContext) error {
|
||||||
|
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get current executable path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := NewFWEnvWriter(HostFWEnvCfgPath, exePath)
|
||||||
|
|
||||||
|
// TODO: configurable from cluster.env
|
||||||
|
return writer.EnsureBootEnv(ctx, BootEnvConfig{
|
||||||
|
BootSource: "usb",
|
||||||
|
BootPart: "A",
|
||||||
|
BootDisk: 0,
|
||||||
|
RootfsAPartNum: 2,
|
||||||
|
RootfsBPartNum: 3,
|
||||||
|
DataPartNum: 4,
|
||||||
|
LinuxRootPrefix: "/dev/sda",
|
||||||
|
})
|
||||||
|
}
|
||||||
60
clitools/pkg/node/uboot/bootenvconfig.go
Normal file
60
clitools/pkg/node/uboot/bootenvconfig.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package uboot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c BootEnvConfig) Validate() error {
|
||||||
|
switch c.BootSource {
|
||||||
|
case "usb", "emmc":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid boot source %q", c.BootSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c.BootPart {
|
||||||
|
case "A", "B":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid boot part %q", c.BootPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BootDisk < 0 {
|
||||||
|
return fmt.Errorf("invalid boot disk %d", c.BootDisk)
|
||||||
|
}
|
||||||
|
if c.RootfsAPartNum <= 0 {
|
||||||
|
return fmt.Errorf("invalid rootfs A part %d", c.RootfsAPartNum)
|
||||||
|
}
|
||||||
|
if c.RootfsBPartNum <= 0 {
|
||||||
|
return fmt.Errorf("invalid rootfs B part %d", c.RootfsBPartNum)
|
||||||
|
}
|
||||||
|
if c.DataPartNum <= 0 {
|
||||||
|
return fmt.Errorf("invalid data part %d", c.DataPartNum)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(c.LinuxRootPrefix) == "" {
|
||||||
|
return fmt.Errorf("linux root prefix is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c BootEnvConfig) bootCmdOrDefault() string {
|
||||||
|
if s := strings.TrimSpace(c.BootCmd); s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return compactUBootScript(defaultBootCmdTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compactUBootScript(s string) string {
|
||||||
|
lines := strings.Split(s, "\n")
|
||||||
|
out := make([]string, 0, len(lines))
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(out, " ")
|
||||||
|
}
|
||||||
143
clitools/pkg/node/uboot/env_writer.go
Normal file
143
clitools/pkg/node/uboot/env_writer.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package uboot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"example.com/monok8s/pkg/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFWEnvWriter(configPath string, ctlPath string) *FWEnvWriter {
|
||||||
|
return &FWEnvWriter{
|
||||||
|
Runner: system.NewRunner(system.RunnerConfig{
|
||||||
|
DefaultTimeout: 15 * time.Second,
|
||||||
|
StreamOutput: false,
|
||||||
|
Logger: &system.StdLogger{},
|
||||||
|
}),
|
||||||
|
ConfigPath: configPath,
|
||||||
|
CtlPath: ctlPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FWEnvWriter) GetEnv(ctx context.Context, key string) (string, error) {
|
||||||
|
key = strings.TrimSpace(key)
|
||||||
|
if key == "" {
|
||||||
|
return "", fmt.Errorf("key is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"internal", "fw-printenv",
|
||||||
|
"--key", key,
|
||||||
|
"--config", w.ConfigPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := w.Runner.RunWithOptions(ctx, w.CtlPath, args, system.RunOptions{Quiet: true})
|
||||||
|
if err != nil {
|
||||||
|
if res != nil {
|
||||||
|
return "", fmt.Errorf("fw-printenv %q: %w (stdout=%q stderr=%q)",
|
||||||
|
key, err, strings.TrimSpace(res.Stdout), strings.TrimSpace(res.Stderr))
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("fw-printenv %q: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := strings.TrimSpace(res.Stdout)
|
||||||
|
if out == "" {
|
||||||
|
return "", fmt.Errorf("empty output for key %q", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(out, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", fmt.Errorf("unexpected fw_printenv output for %q: %q", key, out)
|
||||||
|
}
|
||||||
|
if parts[0] != key {
|
||||||
|
return "", fmt.Errorf("unexpected fw_printenv key: got %q want %q", parts[0], key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FWEnvWriter) SetEnv(ctx context.Context, key, value string) error {
|
||||||
|
key = strings.TrimSpace(key)
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
|
||||||
|
if key == "" {
|
||||||
|
return fmt.Errorf("key is required")
|
||||||
|
}
|
||||||
|
if value == "" {
|
||||||
|
return fmt.Errorf("value is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"internal", "fw-setenv",
|
||||||
|
"--key", key,
|
||||||
|
"--value", value,
|
||||||
|
"--config", w.ConfigPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := w.Runner.RunWithOptions(ctx, w.CtlPath, args, system.RunOptions{Quiet: true})
|
||||||
|
if err != nil {
|
||||||
|
if res != nil {
|
||||||
|
return fmt.Errorf("fw-setenv %q: %w (stdout=%q stderr=%q)",
|
||||||
|
key, err, strings.TrimSpace(res.Stdout), strings.TrimSpace(res.Stderr))
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fw-setenv %q: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FWEnvWriter) SetEnvIfDifferent(ctx context.Context, key, desired string) error {
|
||||||
|
current, err := w.GetEnv(ctx, key)
|
||||||
|
if err == nil && current == desired {
|
||||||
|
klog.V(1).InfoS("fw env already matches", "key", key, "value", desired)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
klog.InfoS("fw env key missing or unreadable, will set",
|
||||||
|
"key", key,
|
||||||
|
"desired", desired,
|
||||||
|
"error", err,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
klog.InfoS("fw env drift detected, updating",
|
||||||
|
"key", key,
|
||||||
|
"current", current,
|
||||||
|
"desired", desired,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.SetEnv(ctx, key, desired)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FWEnvWriter) EnsureBootEnv(ctx context.Context, cfg BootEnvConfig) error {
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
envs := []struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}{
|
||||||
|
{"bootcmd", cfg.bootCmdOrDefault()},
|
||||||
|
{"boot_source", string(cfg.BootSource)},
|
||||||
|
{"boot_part", string(cfg.BootPart)},
|
||||||
|
{"boot_disk", fmt.Sprintf("%d", cfg.BootDisk)},
|
||||||
|
{"rootfs_a_partnum", fmt.Sprintf("%d", cfg.RootfsAPartNum)},
|
||||||
|
{"rootfs_b_partnum", fmt.Sprintf("%d", cfg.RootfsBPartNum)},
|
||||||
|
{"data_partnum", fmt.Sprintf("%d", cfg.DataPartNum)},
|
||||||
|
{"linux_root_prefix", cfg.LinuxRootPrefix},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range envs {
|
||||||
|
if err := w.SetEnvIfDifferent(ctx, kv.Key, kv.Value); err != nil {
|
||||||
|
return fmt.Errorf("ensure %s: %w", kv.Key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package node
|
package uboot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -11,15 +11,11 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"example.com/monok8s/pkg/node"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConfigureUBootCommands(ctx context.Context, n *NodeContext) error {
|
func ConfigureUBootCommands(ctx context.Context, n *node.NodeContext) error {
|
||||||
const (
|
|
||||||
procMTDPath = "/proc/mtd"
|
|
||||||
fwEnvCfgPath = "/etc/fw_env.config"
|
|
||||||
targetName = "uboot-env"
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = n
|
_ = n
|
||||||
|
|
||||||
type mtdInfo struct {
|
type mtdInfo struct {
|
||||||
@@ -165,19 +161,19 @@ func ConfigureUBootCommands(ctx context.Context, n *NodeContext) error {
|
|||||||
klog.Infof("Using: %s", best.config)
|
klog.Infof("Using: %s", best.config)
|
||||||
|
|
||||||
// Avoid rewriting if already identical.
|
// Avoid rewriting if already identical.
|
||||||
existing, err := os.ReadFile(fwEnvCfgPath)
|
existing, err := os.ReadFile(HostFWEnvCfgPath)
|
||||||
if err == nil && bytes.Equal(existing, []byte(best.config)) {
|
if err == nil && bytes.Equal(existing, []byte(best.config)) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("read %s: %w", fwEnvCfgPath, err)
|
return fmt.Errorf("read %s: %w", HostFWEnvCfgPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(fwEnvCfgPath), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(HostFWEnvCfgPath), 0o755); err != nil {
|
||||||
return fmt.Errorf("mkdir %s: %w", filepath.Dir(fwEnvCfgPath), err)
|
return fmt.Errorf("mkdir %s: %w", filepath.Dir(HostFWEnvCfgPath), err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(fwEnvCfgPath, []byte(best.config), 0o644); err != nil {
|
if err := os.WriteFile(HostFWEnvCfgPath, []byte(best.config), 0o644); err != nil {
|
||||||
return fmt.Errorf("write %s: %w", fwEnvCfgPath, err)
|
return fmt.Errorf("write %s: %w", HostFWEnvCfgPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
60
clitools/pkg/node/uboot/types.go
Normal file
60
clitools/pkg/node/uboot/types.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package uboot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"example.com/monok8s/pkg/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
procMTDPath = "/proc/mtd"
|
||||||
|
targetName = "uboot-env"
|
||||||
|
|
||||||
|
HostFWEnvCfgPath = "/etc/fw_env.config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BootEnvConfig struct {
|
||||||
|
BootSource string // usb or emmc
|
||||||
|
BootPart string // A or B
|
||||||
|
BootDisk int
|
||||||
|
RootfsAPartNum int
|
||||||
|
RootfsBPartNum int
|
||||||
|
DataPartNum int
|
||||||
|
LinuxRootPrefix string // /dev/sda or /dev/mmcblk0p
|
||||||
|
BootCmd string // optional; defaults to DefaultBootCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
type FWEnvWriter struct {
|
||||||
|
Runner *system.Runner
|
||||||
|
ConfigPath string
|
||||||
|
CtlPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultBootCmdTemplate = `
|
||||||
|
if test "${boot_source}" = "usb"; then
|
||||||
|
usb start || exit;
|
||||||
|
setenv boot_iface usb;
|
||||||
|
elif test "${boot_source}" = "emmc"; then
|
||||||
|
mmc dev ${boot_disk} || exit;
|
||||||
|
setenv boot_iface mmc;
|
||||||
|
else
|
||||||
|
echo "unsupported boot_source: ${boot_source}";
|
||||||
|
exit;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
setenv kernel_addr_r 0xa0000000;
|
||||||
|
|
||||||
|
if test "${boot_part}" = "A"; then
|
||||||
|
setenv rootpart ${rootfs_a_partnum};
|
||||||
|
elif test "${boot_part}" = "B"; then
|
||||||
|
setenv rootpart ${rootfs_b_partnum};
|
||||||
|
else
|
||||||
|
echo "unsupported boot_part: ${boot_part}";
|
||||||
|
exit;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
setenv bootdev ${boot_disk}:${rootpart};
|
||||||
|
setenv rootdev ${linux_root_prefix}${rootpart};
|
||||||
|
setenv datadev ${linux_root_prefix}${data_partnum};
|
||||||
|
|
||||||
|
setenv bootargs "${bootargs_console} root=${rootdev} data=${datadev} rw rootwait rootfstype=ext4";
|
||||||
|
ext4load ${boot_iface} ${bootdev} ${kernel_addr_r} /boot/kernel.itb && bootm ${kernel_addr_r};
|
||||||
|
`
|
||||||
Reference in New Issue
Block a user