commit abc749a4b05cc2336cd6789100499cf4b1801356a00ebd669b4592c615e6e957
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-04-04T14:47:51Z |
| subject | Writes bootcmd |
commit abc749a4b05cc2336cd6789100499cf4b1801356a00ebd669b4592c615e6e957
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-04-04T14:47:51Z
Writes bootcmd
---
clitools/docker/ctl-agent.Dockerfile | 2 +
clitools/pkg/bootstrap/registry.go | 4 +-
clitools/pkg/bootstrap/runner.go | 5 +
clitools/pkg/cmd/internal/fwprintenv.go | 2 +-
clitools/pkg/cmd/internal/fwsetenv.go | 4 +-
clitools/pkg/node/uboot/ab.go | 30 ++++++
clitools/pkg/node/uboot/bootenvconfig.go | 60 +++++++++++
clitools/pkg/node/uboot/env_writer.go | 143 ++++++++++++++++++++++++++
clitools/pkg/node/{uboot.go => uboot/init.go} | 24 ++---
clitools/pkg/node/uboot/types.go | 60 +++++++++++
10 files changed, 316 insertions(+), 18 deletions(-)
diff --git a/clitools/docker/ctl-agent.Dockerfile b/clitools/docker/ctl-agent.Dockerfile
index 7387bf2..79766a0 100644
--- a/clitools/docker/ctl-agent.Dockerfile
+++ b/clitools/docker/ctl-agent.Dockerfile
@@ -11,5 +11,7 @@ COPY out/fw_printenv ./
COPY out/fw_setenv ./
COPY --from=cacerts /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+ENV PATH=/
+
ENTRYPOINT ["/ctl"]
CMD ["agent"]
diff --git a/clitools/pkg/bootstrap/registry.go b/clitools/pkg/bootstrap/registry.go
index 7bf8e34..5c4a52b 100644
--- a/clitools/pkg/bootstrap/registry.go
+++ b/clitools/pkg/bootstrap/registry.go
@@ -4,6 +4,7 @@ import (
"fmt"
"example.com/monok8s/pkg/node"
+ "example.com/monok8s/pkg/node/uboot"
)
type Registry struct {
@@ -30,7 +31,8 @@ func NewRegistry(ctx *node.NodeContext) *Registry {
"ConfigureDefaultCNI": node.ConfigureDefaultCNI,
"ConfigureHostname": node.ConfigureHostname(netCfg),
"ConfigureMgmtInterface": node.ConfigureMgmtInterface(netCfg),
- "ConfigureUBootCommands": node.ConfigureUBootCommands,
+ "ConfigureABBoot": uboot.ConfigureABBoot,
+ "ConfigureUBootCommands": uboot.ConfigureUBootCommands,
"DetectLocalClusterState": node.DetectLocalClusterState,
"EnsureIPForward": node.EnsureIPForward,
"ReconcileControlPlane": node.ReconcileControlPlane,
diff --git a/clitools/pkg/bootstrap/runner.go b/clitools/pkg/bootstrap/runner.go
index e57d461..d951046 100644
--- a/clitools/pkg/bootstrap/runner.go
+++ b/clitools/pkg/bootstrap/runner.go
@@ -137,6 +137,11 @@ func NewRunner(cfg *monov1alpha1.MonoKSConfig) *Runner {
Name: "Ensure fw_env config and u-boot-tools availablilty",
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",
Name: "Apply daemonset for control agent",
diff --git a/clitools/pkg/cmd/internal/fwprintenv.go b/clitools/pkg/cmd/internal/fwprintenv.go
index 9bc6fde..74e710d 100644
--- a/clitools/pkg/cmd/internal/fwprintenv.go
+++ b/clitools/pkg/cmd/internal/fwprintenv.go
@@ -46,7 +46,7 @@ func newInternalFWPrintEnvCmd() *cobra.Command {
res, err := runner.RunWithOptions(
ctx,
- "/fw_printenv",
+ "fw_printenv",
runArgs,
system.RunOptions{
Quiet: true,
diff --git a/clitools/pkg/cmd/internal/fwsetenv.go b/clitools/pkg/cmd/internal/fwsetenv.go
index 3537e33..fad5425 100644
--- a/clitools/pkg/cmd/internal/fwsetenv.go
+++ b/clitools/pkg/cmd/internal/fwsetenv.go
@@ -52,7 +52,7 @@ func newInternalFWSetEnvCmd() *cobra.Command {
// Preflight first so failure is clearer than blindly writing.
preflightRes, err := runner.RunWithOptions(
ctx,
- "/fw_printenv",
+ "fw_printenv",
[]string{"-c", configPath},
system.RunOptions{
Quiet: true,
@@ -70,7 +70,7 @@ func newInternalFWSetEnvCmd() *cobra.Command {
res, err := runner.RunWithOptions(
ctx,
- "/fw_setenv",
+ "fw_setenv",
[]string{
"-c", configPath,
key, value,
diff --git a/clitools/pkg/node/uboot/ab.go b/clitools/pkg/node/uboot/ab.go
new file mode 100644
index 0000000..80cf256
--- /dev/null
+++ b/clitools/pkg/node/uboot/ab.go
@@ -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",
+ })
+}
diff --git a/clitools/pkg/node/uboot/bootenvconfig.go b/clitools/pkg/node/uboot/bootenvconfig.go
new file mode 100644
index 0000000..c1f0b20
--- /dev/null
+++ b/clitools/pkg/node/uboot/bootenvconfig.go
@@ -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, " ")
+}
diff --git a/clitools/pkg/node/uboot/env_writer.go b/clitools/pkg/node/uboot/env_writer.go
new file mode 100644
index 0000000..0419af4
--- /dev/null
+++ b/clitools/pkg/node/uboot/env_writer.go
@@ -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
+}
diff --git a/clitools/pkg/node/uboot.go b/clitools/pkg/node/uboot/init.go
similarity index 90%
rename from clitools/pkg/node/uboot.go
rename to clitools/pkg/node/uboot/init.go
index f53bfd8..27affa7 100644
--- a/clitools/pkg/node/uboot.go
+++ b/clitools/pkg/node/uboot/init.go
@@ -1,4 +1,4 @@
-package node
+package uboot
import (
"bytes"
@@ -11,15 +11,11 @@ import (
"regexp"
"strings"
"time"
-)
-func ConfigureUBootCommands(ctx context.Context, n *NodeContext) error {
- const (
- procMTDPath = "/proc/mtd"
- fwEnvCfgPath = "/etc/fw_env.config"
- targetName = "uboot-env"
- )
+ "example.com/monok8s/pkg/node"
+)
+func ConfigureUBootCommands(ctx context.Context, n *node.NodeContext) error {
_ = n
type mtdInfo struct {
@@ -165,19 +161,19 @@ func ConfigureUBootCommands(ctx context.Context, n *NodeContext) error {
klog.Infof("Using: %s", best.config)
// Avoid rewriting if already identical.
- existing, err := os.ReadFile(fwEnvCfgPath)
+ existing, err := os.ReadFile(HostFWEnvCfgPath)
if err == nil && bytes.Equal(existing, []byte(best.config)) {
return nil
}
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 {
- return fmt.Errorf("mkdir %s: %w", filepath.Dir(fwEnvCfgPath), err)
+ if err := os.MkdirAll(filepath.Dir(HostFWEnvCfgPath), 0o755); err != nil {
+ return fmt.Errorf("mkdir %s: %w", filepath.Dir(HostFWEnvCfgPath), err)
}
- if err := os.WriteFile(fwEnvCfgPath, []byte(best.config), 0o644); err != nil {
- return fmt.Errorf("write %s: %w", fwEnvCfgPath, err)
+ if err := os.WriteFile(HostFWEnvCfgPath, []byte(best.config), 0o644); err != nil {
+ return fmt.Errorf("write %s: %w", HostFWEnvCfgPath, err)
}
return nil
diff --git a/clitools/pkg/node/uboot/types.go b/clitools/pkg/node/uboot/types.go
new file mode 100644
index 0000000..acf034f
--- /dev/null
+++ b/clitools/pkg/node/uboot/types.go
@@ -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};
+`