Reduce the number of bootenv vars
This commit is contained in:
@@ -10,7 +10,7 @@ https://docs.mono.si/gateway-development-kit/getting-started
|
|||||||
|
|
||||||
## IMPORTANT NOTES
|
## IMPORTANT NOTES
|
||||||
* This is not your everyday linux image! It is best suited for users that is already familiar
|
* This is not your everyday linux image! It is best suited for users that is already familiar
|
||||||
with k8s. For first-timers, you may want to try the default config that gives you a ready-to-used
|
with k8s. For first-timers, you may want to try the default config that gives you a ready-to-use
|
||||||
cluster then get yourself started from there
|
cluster then get yourself started from there
|
||||||
|
|
||||||
* The 3 RJ45 ports are label in eth1, eth2, eth0 respectively by the kernel (left to right)
|
* The 3 RJ45 ports are label in eth1, eth2, eth0 respectively by the kernel (left to right)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ TAG=dev
|
|||||||
|
|
||||||
# The Linux kernel, from NXP
|
# The Linux kernel, from NXP
|
||||||
NXP_VERSION=lf-6.18.2-1.0.0
|
NXP_VERSION=lf-6.18.2-1.0.0
|
||||||
CRIO_VERSION=cri-o.arm64.v1.35.2
|
CRIO_VERSION=cri-o.arm64.v1.33.3
|
||||||
KUBE_VERSION=v1.34.1
|
KUBE_VERSION=v1.33.3
|
||||||
|
|
||||||
# Mono's tutorial said fsl-ls1046a-rdb.dtb but our shipped board is not that one
|
# Mono's tutorial said fsl-ls1046a-rdb.dtb but our shipped board is not that one
|
||||||
# We need fsl-ls1046a-rdb-sdk.dtb here
|
# We need fsl-ls1046a-rdb-sdk.dtb here
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package osupgrade
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
"example.com/monok8s/pkg/catalog"
|
"example.com/monok8s/pkg/catalog"
|
||||||
"example.com/monok8s/pkg/controller/osimage"
|
"example.com/monok8s/pkg/controller/osimage"
|
||||||
"example.com/monok8s/pkg/kube"
|
"example.com/monok8s/pkg/kube"
|
||||||
|
"example.com/monok8s/pkg/node/uboot"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
|
func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
|
||||||
@@ -89,7 +91,7 @@ func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
|
|||||||
|
|
||||||
imageOptions := osimage.ApplyOptions{
|
imageOptions := osimage.ApplyOptions{
|
||||||
URL: first.URL,
|
URL: first.URL,
|
||||||
TargetPath: "./out/flash.img",
|
TargetPath: "/dev/sda?",
|
||||||
ExpectedRawSHA256: imageSHA,
|
ExpectedRawSHA256: imageSHA,
|
||||||
ExpectedRawSize: first.Size,
|
ExpectedRawSize: first.Size,
|
||||||
BufferSize: 6 * 1024 * 1024,
|
BufferSize: 6 * 1024 * 1024,
|
||||||
@@ -128,10 +130,9 @@ func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
|
|||||||
}
|
}
|
||||||
|
|
||||||
klog.Info(result)
|
klog.Info(result)
|
||||||
if err := SetNextBootEnv(ctx, NextBootConfig{
|
|
||||||
Key: "boot_part",
|
cfgPath := os.Getenv("FW_ENV_CONFIG_FILE")
|
||||||
Value: "B",
|
if err := uboot.ConfigureNextBoot(ctx, cfgPath); err != nil {
|
||||||
}); err != nil {
|
|
||||||
return failProgress(ctx, clients, osup, "set boot env", err)
|
return failProgress(ctx, clients, osup, "set boot env", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,5 +146,8 @@ func HandleOSUpgrade(ctx context.Context, clients *kube.Clients,
|
|||||||
return fmt.Errorf("update progress status: %w", err)
|
return fmt.Errorf("update progress status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Drain the node here
|
||||||
|
// TODO: Issue Reboot
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,16 @@ func applyControlAgentClusterRole(ctx context.Context, kubeClient kubernetes.Int
|
|||||||
Resources: []string{"osupgrades/status"},
|
Resources: []string{"osupgrades/status"},
|
||||||
Verbs: []string{"get", "patch", "update"},
|
Verbs: []string{"get", "patch", "update"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
APIGroups: []string{monov1alpha1.Group},
|
||||||
|
Resources: []string{"osupgradeprogresses"},
|
||||||
|
Verbs: []string{"get", "list", "watch", "create", "patch", "update"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIGroups: []string{monov1alpha1.Group},
|
||||||
|
Resources: []string{"osupgradeprogresses/status"},
|
||||||
|
Verbs: []string{"get", "list", "watch", "create", "patch", "update"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
APIGroups: []string{""},
|
APIGroups: []string{""},
|
||||||
Resources: []string{"nodes"},
|
Resources: []string{"nodes"},
|
||||||
|
|||||||
@@ -21,10 +21,37 @@ func ConfigureABBoot(ctx context.Context, nctx *node.NodeContext) error {
|
|||||||
return writer.EnsureBootEnv(ctx, BootEnvConfig{
|
return writer.EnsureBootEnv(ctx, BootEnvConfig{
|
||||||
BootSource: "usb",
|
BootSource: "usb",
|
||||||
BootPart: "A",
|
BootPart: "A",
|
||||||
BootDisk: 0,
|
})
|
||||||
RootfsAPartNum: 2,
|
}
|
||||||
RootfsBPartNum: 3,
|
|
||||||
DataPartNum: 4,
|
// This is called by the agent controller/osupgrade/handler.go
|
||||||
LinuxRootPrefix: "/dev/sda",
|
func ConfigureNextBoot(ctx context.Context, fwEnvCfgPath string) error {
|
||||||
|
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get current executable path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := NewFWEnvWriter(fwEnvCfgPath, exePath)
|
||||||
|
|
||||||
|
currBootPart, err := writer.GetEnv(ctx, "boot_part")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get boot_part: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
next := "A"
|
||||||
|
if currBootPart == "A" {
|
||||||
|
next = "B"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
currBootSource, err := writer.GetEnv(ctx, "boot_source")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get boot_source: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return writer.EnsureBootEnv(ctx, BootEnvConfig{
|
||||||
|
BootSource: currBootSource,
|
||||||
|
BootPart: next,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
12
clitools/pkg/node/uboot/bootcmd_test.go
Normal file
12
clitools/pkg/node/uboot/bootcmd_test.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package uboot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBootCmd(_ *testing.T) {
|
||||||
|
|
||||||
|
cfg := BootEnvConfig{}
|
||||||
|
fmt.Println(cfg.bootCmdOrDefault())
|
||||||
|
}
|
||||||
@@ -18,22 +18,6 @@ func (c BootEnvConfig) Validate() error {
|
|||||||
return fmt.Errorf("invalid boot part %q", c.BootPart)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,11 +126,6 @@ func (w *FWEnvWriter) EnsureBootEnv(ctx context.Context, cfg BootEnvConfig) erro
|
|||||||
{"bootcmd", cfg.bootCmdOrDefault()},
|
{"bootcmd", cfg.bootCmdOrDefault()},
|
||||||
{"boot_source", string(cfg.BootSource)},
|
{"boot_source", string(cfg.BootSource)},
|
||||||
{"boot_part", string(cfg.BootPart)},
|
{"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 {
|
for _, kv := range envs {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package uboot
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
"os"
|
"os"
|
||||||
@@ -155,7 +156,7 @@ func ConfigureUBootCommands(ctx context.Context, n *node.NodeContext) error {
|
|||||||
b.WriteString(trimOutput(a.output, 600))
|
b.WriteString(trimOutput(a.output, 600))
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
}
|
}
|
||||||
return fmt.Errorf(b.String())
|
return errors.New(b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
klog.Infof("Using: %s", best.config)
|
klog.Infof("Using: %s", best.config)
|
||||||
|
|||||||
@@ -14,11 +14,6 @@ const (
|
|||||||
type BootEnvConfig struct {
|
type BootEnvConfig struct {
|
||||||
BootSource string // usb or emmc
|
BootSource string // usb or emmc
|
||||||
BootPart string // A or B
|
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
|
BootCmd string // optional; defaults to DefaultBootCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,32 +24,52 @@ type FWEnvWriter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultBootCmdTemplate = `
|
const defaultBootCmdTemplate = `
|
||||||
|
setenv kernel_addr_r 0xa0000000;
|
||||||
|
|
||||||
|
if usb start; then
|
||||||
|
if usb dev 0; then
|
||||||
|
echo "Trying generic USB boot...";
|
||||||
|
|
||||||
|
# Prefer extlinux-style boot
|
||||||
|
if test -e usb 0:1 /boot/extlinux/extlinux.conf; then
|
||||||
|
echo "Found extlinux config on USB";
|
||||||
|
sysboot usb 0:1 any ${kernel_addr_r} /boot/extlinux/extlinux.conf;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
# Fallback: U-Boot script image
|
||||||
|
if test -e usb 0:1 /boot/boot.scr; then
|
||||||
|
echo "Found boot.scr on USB";
|
||||||
|
if load usb 0:1 ${kernel_addr_r} /boot/boot.scr; then
|
||||||
|
source ${kernel_addr_r};
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
|
||||||
if test "${boot_source}" = "usb"; then
|
if test "${boot_source}" = "usb"; then
|
||||||
usb start || exit;
|
usb start || exit;
|
||||||
setenv boot_iface usb;
|
setenv boot_iface usb;
|
||||||
|
usb dev 0 || exit;
|
||||||
elif test "${boot_source}" = "emmc"; then
|
elif test "${boot_source}" = "emmc"; then
|
||||||
mmc dev ${boot_disk} || exit;
|
mmc dev 0 || exit;
|
||||||
setenv boot_iface mmc;
|
setenv boot_iface mmc;
|
||||||
else
|
else
|
||||||
echo "unsupported boot_source: ${boot_source}";
|
echo "unsupported boot_source: ${boot_source}";
|
||||||
exit;
|
exit;
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
setenv kernel_addr_r 0xa0000000;
|
|
||||||
|
|
||||||
if test "${boot_part}" = "A"; then
|
if test "${boot_part}" = "A"; then
|
||||||
setenv rootpart ${rootfs_a_partnum};
|
setenv rootpart 2;
|
||||||
elif test "${boot_part}" = "B"; then
|
elif test "${boot_part}" = "B"; then
|
||||||
setenv rootpart ${rootfs_b_partnum};
|
setenv rootpart 3;
|
||||||
else
|
else
|
||||||
echo "unsupported boot_part: ${boot_part}";
|
echo "unsupported boot_part: ${boot_part}";
|
||||||
exit;
|
exit;
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
setenv bootdev ${boot_disk}:${rootpart};
|
setenv bootdev 0:${rootpart};
|
||||||
setenv rootdev ${linux_root_prefix}${rootpart};
|
setenv rootdev ${boot_source}:${rootpart};
|
||||||
setenv datadev ${linux_root_prefix}${data_partnum};
|
|
||||||
|
|
||||||
setenv bootargs "${bootargs_console} root=${rootdev} data=${datadev} rw rootwait rootfstype=ext4";
|
setenv bootargs "${bootargs_console} root=${rootdev} bootpart=${boot_part} rw rootwait rootfstype=ext4";
|
||||||
ext4load ${boot_iface} ${bootdev} ${kernel_addr_r} /boot/kernel.itb && bootm ${kernel_addr_r};
|
ext4load ${boot_iface} ${bootdev} ${kernel_addr_r} /boot/kernel.itb && bootm ${kernel_addr_r};
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -33,6 +33,18 @@ MKS_INIT_CONTROL_PLANE=yes
|
|||||||
# OSUpgrade agent
|
# OSUpgrade agent
|
||||||
MKS_ENABLE_CONTROL_AGENT=yes
|
MKS_ENABLE_CONTROL_AGENT=yes
|
||||||
|
|
||||||
|
# Boot configs
|
||||||
|
# usb, emmc
|
||||||
|
MKS_BOOT_SOURCE="usb"
|
||||||
|
MKS_BOOT_DEV_PREFIX="/dev/sda"
|
||||||
|
|
||||||
|
# Depends on your full disk image. This is the default
|
||||||
|
MKS_BOOT_PART=A
|
||||||
|
MKS_BOOT_DISK=0
|
||||||
|
MKS_ROOTFS_PART_A=2
|
||||||
|
MKS_ROOTFS_PART_B=3
|
||||||
|
MKS_ROOTFS_PART_D=4
|
||||||
|
|
||||||
MKS_API_SERVER_ENDPOINT=
|
MKS_API_SERVER_ENDPOINT=
|
||||||
MKS_BOOTSTRAP_TOKEN=
|
MKS_BOOTSTRAP_TOKEN=
|
||||||
MKS_DISCOVERY_TOKEN_CA_CERT_HASH=
|
MKS_DISCOVERY_TOKEN_CA_CERT_HASH=
|
||||||
|
|||||||
11
docs/ota.md
11
docs/ota.md
@@ -1,10 +1,12 @@
|
|||||||
## Upgrade process
|
## Upgrade process
|
||||||
|
|
||||||
We use a CRD with an agent to handle this. Our versions follows upstream's.
|
We use an agent to watch the OSUpgrade CRD to handle this. Our image versions follows upstream.
|
||||||
|
|
||||||
To issue an upgrade. Simply use
|
To issue an upgrade. Simply use
|
||||||
|
|
||||||
kubectl apply -f upgrade.yaml
|
kubectl apply -f upgrade.yaml
|
||||||
|
|
||||||
|
Example yaml
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: monok8s.io/v1alpha1
|
apiVersion: monok8s.io/v1alpha1
|
||||||
kind: OSUpgrade
|
kind: OSUpgrade
|
||||||
@@ -12,8 +14,6 @@ metadata:
|
|||||||
name: "my-ugrade-2"
|
name: "my-ugrade-2"
|
||||||
spec:
|
spec:
|
||||||
version: "v1.35.3"
|
version: "v1.35.3"
|
||||||
imageURL: "https://updates.example.com/monok8s-1.2.3.img.zst"
|
|
||||||
checksum: "sha256:..."
|
|
||||||
nodeSelector: {}
|
nodeSelector: {}
|
||||||
catalog:
|
catalog:
|
||||||
inline: |
|
inline: |
|
||||||
@@ -39,7 +39,7 @@ spec:
|
|||||||
- v1.34.0
|
- v1.34.0
|
||||||
```
|
```
|
||||||
|
|
||||||
catalog accepts URL or ConfigMap
|
catalog also accepts URL or ※ConfigMap
|
||||||
```yaml
|
```yaml
|
||||||
catalog:
|
catalog:
|
||||||
URL: https://example.com/images.yaml
|
URL: https://example.com/images.yaml
|
||||||
@@ -48,6 +48,9 @@ catalog:
|
|||||||
ConfigMap: images-cm
|
ConfigMap: images-cm
|
||||||
```
|
```
|
||||||
|
|
||||||
|
※ ConfigMap requires additional RBAC permission which is not by default. You edit the
|
||||||
|
control-agent ClusterRole and add `cnofigmaps: get` to allow this.
|
||||||
|
|
||||||
Contents should look like this
|
Contents should look like this
|
||||||
```yaml
|
```yaml
|
||||||
stable: v1.35.1
|
stable: v1.35.1
|
||||||
|
|||||||
@@ -42,6 +42,168 @@ wait_for_path() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_cmdline_arg() {
|
||||||
|
key="$1"
|
||||||
|
for arg in $(cat /proc/cmdline); do
|
||||||
|
case "$arg" in
|
||||||
|
"$key"=*)
|
||||||
|
echo "${arg#"$key"=}"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read KEY=VALUE pairs from /sys/class/block/*/uevent without spawning grep/cut.
|
||||||
|
get_uevent_value() {
|
||||||
|
file="$1"
|
||||||
|
want_key="$2"
|
||||||
|
|
||||||
|
[ -f "$file" ] || return 1
|
||||||
|
|
||||||
|
while IFS='=' read -r k v; do
|
||||||
|
[ "$k" = "$want_key" ] && {
|
||||||
|
echo "$v"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
done < "$file"
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return the /dev/<partition> path for the first partition whose GPT PARTNAME matches.
|
||||||
|
find_first_part_by_partname() {
|
||||||
|
want_label="$1"
|
||||||
|
|
||||||
|
for p in /sys/class/block/*; do
|
||||||
|
[ -f "$p/partition" ] || continue
|
||||||
|
|
||||||
|
partname="$(get_uevent_value "$p/uevent" PARTNAME || true)"
|
||||||
|
[ "$partname" = "$want_label" ] || continue
|
||||||
|
|
||||||
|
devname="$(basename "$p")"
|
||||||
|
echo "/dev/$devname"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_partnames() {
|
||||||
|
timeout="${1:-3}"
|
||||||
|
shift
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt "$timeout" ]; do
|
||||||
|
all_found=1
|
||||||
|
for name in "$@"; do
|
||||||
|
if ! find_first_part_by_partname "$name" >/dev/null; then
|
||||||
|
all_found=0
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$all_found" -eq 1 ] && return 0
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
i=$((i + 1))
|
||||||
|
log "Still waiting for $@ to populate($i)"
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
find_part_by_partuuid() {
|
||||||
|
want="$1"
|
||||||
|
|
||||||
|
for p in /sys/class/block/*; do
|
||||||
|
[ -f "$p/partition" ] || continue
|
||||||
|
|
||||||
|
partuuid="$(get_uevent_value "$p/uevent" PARTUUID || true)"
|
||||||
|
[ "$partuuid" = "$want" ] || continue
|
||||||
|
|
||||||
|
echo "/dev/$(basename "$p")"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return the parent disk name for a partition device name.
|
||||||
|
# Examples:
|
||||||
|
# sda2 -> sda
|
||||||
|
# mmcblk0p2 -> mmcblk0
|
||||||
|
parent_disk_name_for_part() {
|
||||||
|
part_devname="$1"
|
||||||
|
|
||||||
|
real="$(readlink -f "/sys/class/block/$part_devname")" || return 1
|
||||||
|
parent="$(basename "$(dirname "$real")")" || return 1
|
||||||
|
|
||||||
|
echo "$parent"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find a sibling partition on the same disk by GPT PARTNAME.
|
||||||
|
find_sibling_part_on_same_disk() {
|
||||||
|
part_path="$1"
|
||||||
|
want_label="$2"
|
||||||
|
|
||||||
|
part_devname="$(basename "$part_path")"
|
||||||
|
disk_devname="$(parent_disk_name_for_part "$part_devname")" || return 1
|
||||||
|
|
||||||
|
for p in /sys/class/block/"$disk_devname"*; do
|
||||||
|
[ -f "$p/partition" ] || continue
|
||||||
|
|
||||||
|
partname="$(get_uevent_value "$p/uevent" PARTNAME || true)"
|
||||||
|
[ "$partname" = "$want_label" ] || continue
|
||||||
|
|
||||||
|
echo "/dev/$(basename "$p")"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve preferred root device from sysfs.
|
||||||
|
# Prefer PARTUUID first, then optionally filesystem UUID if explicitly provided.
|
||||||
|
resolve_preferred_root() {
|
||||||
|
pref_root="$1"
|
||||||
|
|
||||||
|
[ -n "$pref_root" ] || return 1
|
||||||
|
find_part_by_partuuid "$pref_root"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Decide which root PARTNAME we want for the requested slot.
|
||||||
|
# Keep a compatibility fallback for legacy "rootfs" as slot A.
|
||||||
|
wanted_root_labels_for_slot() {
|
||||||
|
slot="$1"
|
||||||
|
|
||||||
|
case "$slot" in
|
||||||
|
B|b)
|
||||||
|
echo "rootfsB"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Try modern rootfsA first, then legacy rootfs
|
||||||
|
echo "rootfsA rootfs"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
find_fallback_root_for_slot() {
|
||||||
|
slot="$1"
|
||||||
|
|
||||||
|
for label in $(wanted_root_labels_for_slot "$slot"); do
|
||||||
|
dev="$(find_first_part_by_partname "$label" || true)"
|
||||||
|
if [ -n "$dev" ]; then
|
||||||
|
echo "$dev"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
mkdir -p /dev /proc /sys /run
|
mkdir -p /dev /proc /sys /run
|
||||||
mount_or_panic -t devtmpfs devtmpfs /dev
|
mount_or_panic -t devtmpfs devtmpfs /dev
|
||||||
mount_or_panic -t proc proc /proc
|
mount_or_panic -t proc proc /proc
|
||||||
@@ -62,45 +224,56 @@ log "Booting kernel took $(cut -d' ' -f1 /proc/uptime) seconds."
|
|||||||
|
|
||||||
. /etc/build-info || panic "failed to source /etc/build-info"
|
. /etc/build-info || panic "failed to source /etc/build-info"
|
||||||
|
|
||||||
ROOT_DEV=""
|
wait_for_partnames 30 rootfsA rootfsB data || panic "failed to wait for fs"
|
||||||
DATA_DEV=""
|
|
||||||
|
|
||||||
for arg in $(cat /proc/cmdline); do
|
ROOT_CMD="$(get_cmdline_arg root || true)"
|
||||||
case "$arg" in
|
BOOT_PART="$(get_cmdline_arg bootpart || true)"
|
||||||
root=*)
|
PREFERRED_PARTUUID="$(get_cmdline_arg pref_root || true)"
|
||||||
ROOT_DEV="${arg#root=}"
|
|
||||||
;;
|
|
||||||
data=*)
|
|
||||||
DATA_DEV="${arg#data=}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
[ -n "$ROOT_DEV" ] || {
|
ROOT_DEV="$(resolve_preferred_root "$PREFERRED_PARTUUID" || true)"
|
||||||
log "No root= specified in cmdline"
|
if [ -n "$ROOT_DEV" ]; then
|
||||||
exec sh
|
log "Using preferred root device: $ROOT_DEV"
|
||||||
}
|
fi
|
||||||
|
|
||||||
[ -n "$DATA_DEV" ] || {
|
if [ -z "$ROOT_DEV" ]; then
|
||||||
log "No data= specified in cmdline"
|
ROOT_DEV="$(find_fallback_root_for_slot "$BOOT_PART" || true)"
|
||||||
exec sh
|
if [ -n "$ROOT_DEV" ]; then
|
||||||
}
|
log "Preferred root not found. Falling back to first valid root device: $ROOT_DEV"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -n "$ROOT_DEV" ] || panic "no usable root device found"
|
||||||
|
|
||||||
|
DATA_DEV="$(find_sibling_part_on_same_disk "$ROOT_DEV" data || true)"
|
||||||
|
|
||||||
|
[ -n "$DATA_DEV" ] || panic "no data partition found on same disk as $ROOT_DEV"
|
||||||
|
|
||||||
|
wait_for_path "$ROOT_DEV"
|
||||||
wait_for_path "$DATA_DEV"
|
wait_for_path "$DATA_DEV"
|
||||||
|
|
||||||
e2fsck -p "$DATA_DEV" || {
|
e2fsck -p "$DATA_DEV" || {
|
||||||
echo "Auto fsck failed, forcing repair"
|
log "Auto fsck failed, forcing repair"
|
||||||
e2fsck -y "$DATA_DEV" || panic "fsck failed on $DATA_DEV"
|
e2fsck -y "$DATA_DEV" || panic "fsck failed on $DATA_DEV"
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdir -p /newroot
|
mkdir -p /newroot
|
||||||
|
mkdir -p /newroot/data
|
||||||
|
mkdir -p /newroot/var
|
||||||
|
|
||||||
mount_retry "$ROOT_DEV" /newroot ext4 ro
|
mount_retry "$ROOT_DEV" /newroot ext4 ro
|
||||||
mount_retry "$DATA_DEV" /newroot/data ext4 rw
|
mount_retry "$DATA_DEV" /newroot/data ext4 rw
|
||||||
|
|
||||||
|
mkdir -p /newroot/data/var
|
||||||
|
mkdir -p /newroot/data/etc-overlay/upper
|
||||||
|
mkdir -p /newroot/data/etc-overlay/work
|
||||||
|
|
||||||
mount_or_panic --bind /newroot/data/var /newroot/var
|
mount_or_panic --bind /newroot/data/var /newroot/var
|
||||||
|
|
||||||
|
# BusyBox mount just needs a normal -o option string here.
|
||||||
|
# The important bit is that overlayfs itself requires lowerdir/upperdir/workdir,
|
||||||
|
# and workdir must live on the same filesystem as upperdir.
|
||||||
mount_or_panic -t overlay overlay \
|
mount_or_panic -t overlay overlay \
|
||||||
-o lowerdir=/newroot/etc,upperdir=/newroot/data/etc-overlay/upper,workdir=/newroot/data/etc-overlay/work \
|
-o "lowerdir=/newroot/etc,upperdir=/newroot/data/etc-overlay/upper,workdir=/newroot/data/etc-overlay/work" \
|
||||||
/newroot/etc
|
/newroot/etc
|
||||||
|
|
||||||
mount_or_panic --move /dev /newroot/dev
|
mount_or_panic --move /dev /newroot/dev
|
||||||
@@ -108,7 +281,7 @@ mount_or_panic --move /proc /newroot/proc
|
|||||||
mount_or_panic --move /sys /newroot/sys
|
mount_or_panic --move /sys /newroot/sys
|
||||||
mount_or_panic --move /run /newroot/run
|
mount_or_panic --move /run /newroot/run
|
||||||
|
|
||||||
log "Switching root to $ROOT_DEV"
|
log "Switching root to $ROOT_DEV (data: $DATA_DEV, slot: $BOOT_PART)"
|
||||||
exec switch_root /newroot /sbin/init
|
exec switch_root /newroot /sbin/init
|
||||||
|
|
||||||
panic "switch_root returned unexpectedly"
|
panic "switch_root returned unexpectedly"
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
### User-configurable vars
|
### User-configurable vars
|
||||||
IMG="./out/monok8s-dev.img.gz"
|
IMG_DIR="./out"
|
||||||
DEFAULT_DISK="/dev/disk7" # whole disk, not a partition
|
|
||||||
CONFIG_PART_SUFFIX="s1" # config partition after flashing
|
CONFIG_PART_SUFFIX="s1" # config partition after flashing
|
||||||
ENV_SEARCH_DIR="./out" # optional; leave empty to use image dir
|
ENV_SEARCH_DIR="./out" # optional; leave empty to use selected image dir
|
||||||
|
|
||||||
MNT="/tmp/monok8s-usb-config.$$"
|
MNT="/tmp/monok8s-usb-config.$$"
|
||||||
|
|
||||||
@@ -59,6 +58,131 @@ wait_for_mountable_partition() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select_image() {
|
||||||
|
dir="$1"
|
||||||
|
|
||||||
|
[ -d "$dir" ] || die "Image directory not found: $dir"
|
||||||
|
|
||||||
|
OLD_IFS="${IFS}"
|
||||||
|
IFS='
|
||||||
|
'
|
||||||
|
set -- $(find "$dir" -maxdepth 1 -type f -name '*.img.gz' | sort)
|
||||||
|
IFS="${OLD_IFS}"
|
||||||
|
|
||||||
|
[ "$#" -gt 0 ] || die "No .img.gz images found in $dir"
|
||||||
|
|
||||||
|
echo "Available images:"
|
||||||
|
n=1
|
||||||
|
for img in "$@"; do
|
||||||
|
size="$(stat -f%z "$img" 2>/dev/null || echo "?")"
|
||||||
|
echo " [$n] $(basename "$img") (${size} bytes)"
|
||||||
|
n=$((n + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
printf "Select image number: "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
''|*[!0-9]*)
|
||||||
|
die "Invalid selection: $choice"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
n=1
|
||||||
|
for img in "$@"; do
|
||||||
|
if [ "$n" -eq "$choice" ]; then
|
||||||
|
SELECTED_IMG="$img"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
n=$((n + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
die "Selection out of range: $choice"
|
||||||
|
}
|
||||||
|
|
||||||
|
list_candidate_disks() {
|
||||||
|
found=0
|
||||||
|
for dev in /dev/disk*; do
|
||||||
|
base="$(basename "$dev")"
|
||||||
|
|
||||||
|
case "$base" in
|
||||||
|
disk[0-9])
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
info="$(diskutil list "$dev" 2>/dev/null || true)"
|
||||||
|
[ -n "$info" ] || continue
|
||||||
|
|
||||||
|
# Skip Apple disks
|
||||||
|
echo "$info" | grep -q "APFS" && continue
|
||||||
|
|
||||||
|
info="$(diskutil info "$dev" 2>/dev/null || true)"
|
||||||
|
found=1
|
||||||
|
|
||||||
|
media_name="$(echo "$info" | sed -n 's/^ *Device \/ Media Name: *//p' | head -n1)"
|
||||||
|
protocol="$(echo "$info" | sed -n 's/^ *Protocol: *//p' | head -n1)"
|
||||||
|
size="$(echo "$info" | sed -n 's/^ *Disk Size: *//p' | head -n1)"
|
||||||
|
|
||||||
|
echo "$dev|$media_name|$protocol|$size"
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$found" -eq 1 ] || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
select_disk() {
|
||||||
|
candidates="$(list_candidate_disks || true)"
|
||||||
|
|
||||||
|
if [ -z "$candidates" ]; then
|
||||||
|
warn "No obvious external candidate disks found."
|
||||||
|
printf "Enter target disk manually (example: /dev/disk7): "
|
||||||
|
read -r TARGET_DISK
|
||||||
|
[ -n "$TARGET_DISK" ] || die "No disk entered"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Candidate target disks:"
|
||||||
|
n=1
|
||||||
|
echo "$candidates" | while IFS='|' read -r dev media protocol size; do
|
||||||
|
echo " [$n] $dev - $media - $protocol - $size"
|
||||||
|
n=$((n + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
printf "Select disk number (or type a full /dev/diskN path): "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
/dev/disk[0-9]*)
|
||||||
|
TARGET_DISK="$choice"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
''|*[!0-9]*)
|
||||||
|
die "Invalid selection: $choice"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
n=1
|
||||||
|
OLD_IFS="${IFS}"
|
||||||
|
IFS='
|
||||||
|
'
|
||||||
|
for line in $candidates; do
|
||||||
|
if [ "$n" -eq "$choice" ]; then
|
||||||
|
TARGET_DISK="$(echo "$line" | cut -d'|' -f1)"
|
||||||
|
IFS="${OLD_IFS}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
n=$((n + 1))
|
||||||
|
done
|
||||||
|
IFS="${OLD_IFS}"
|
||||||
|
|
||||||
|
die "Selection out of range: $choice"
|
||||||
|
}
|
||||||
|
|
||||||
### Sanity checks
|
### Sanity checks
|
||||||
require_cmd diskutil
|
require_cmd diskutil
|
||||||
require_cmd gunzip
|
require_cmd gunzip
|
||||||
@@ -70,24 +194,26 @@ require_cmd cp
|
|||||||
require_cmd sed
|
require_cmd sed
|
||||||
require_cmd stat
|
require_cmd stat
|
||||||
require_cmd pv
|
require_cmd pv
|
||||||
|
require_cmd cut
|
||||||
|
require_cmd grep
|
||||||
|
require_cmd head
|
||||||
|
|
||||||
printf "disk ($DEFAULT_DISK): "
|
select_image "$IMG_DIR"
|
||||||
read -r TARGET_DISK
|
IMG="$SELECTED_IMG"
|
||||||
|
|
||||||
if [ -z "$TARGET_DISK" ]; then
|
select_disk
|
||||||
TARGET_DISK="$DEFAULT_DISK"
|
|
||||||
fi
|
|
||||||
|
|
||||||
TARGET_RAW_DISK="/dev/r$(basename "$TARGET_DISK")"
|
TARGET_RAW_DISK="/dev/r$(basename "$TARGET_DISK")"
|
||||||
TARGET_BASENAME="$(basename "$TARGET_DISK")"
|
TARGET_BASENAME="$(basename "$TARGET_DISK")"
|
||||||
|
|
||||||
### Derived vars
|
### Derived vars
|
||||||
TARGET_CONFIG_PART="/dev/${TARGET_BASENAME}${CONFIG_PART_SUFFIX}"
|
TARGET_CONFIG_PART="/dev/${TARGET_BASENAME}${CONFIG_PART_SUFFIX}"
|
||||||
IMG_DIR="$(cd "$(dirname "$IMG")" && pwd)"
|
IMG_DIR_ABS="$(cd "$(dirname "$IMG")" && pwd)"
|
||||||
SEARCH_DIR="${ENV_SEARCH_DIR:-$IMG_DIR}"
|
SEARCH_DIR="${ENV_SEARCH_DIR:-$IMG_DIR_ABS}"
|
||||||
IMG_SIZE_BYTES="$(stat -f%z "$IMG")"
|
|
||||||
|
|
||||||
[ -f "$IMG" ] || die "Image not found: $IMG"
|
[ -f "$IMG" ] || die "Image not found: $IMG"
|
||||||
|
IMG_SIZE_BYTES="$(stat -f%z "$IMG")"
|
||||||
|
|
||||||
[ -b "$TARGET_DISK" ] || die "Target disk not found: $TARGET_DISK"
|
[ -b "$TARGET_DISK" ] || die "Target disk not found: $TARGET_DISK"
|
||||||
[ "$(id -u)" -eq 0 ] || die "Run this script with sudo"
|
[ "$(id -u)" -eq 0 ] || die "Run this script with sudo"
|
||||||
|
|
||||||
@@ -122,7 +248,7 @@ diskutil unmountDisk force "$TARGET_DISK" || die "Failed to unmount $TARGET_DISK
|
|||||||
log "Flashing image with progress..."
|
log "Flashing image with progress..."
|
||||||
pv -s "$IMG_SIZE_BYTES" "$IMG" \
|
pv -s "$IMG_SIZE_BYTES" "$IMG" \
|
||||||
| gunzip \
|
| gunzip \
|
||||||
| dd of="$TARGET_RAW_DISK" bs=1m
|
| dd of="$TARGET_RAW_DISK" bs=4m
|
||||||
|
|
||||||
log "Syncing writes..."
|
log "Syncing writes..."
|
||||||
sync
|
sync
|
||||||
|
|||||||
Reference in New Issue
Block a user