mirror of
https://github.com/kairos-io/kairos-sdk.git
synced 2025-06-29 08:26:59 +00:00
* ⚠️ 🎨 ✨ Use immucore Build kairos with immmucore instead of cos-immutable-rootfs Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * 🐛 Fix state sdk State sdk is using ghw to identify the state of the disks, but unfortunately ghw only works with devices and not with labels This patch adds a workaround by checking deeper for partitions that migth not have mountpoints reported by ghw but are indeed mounted Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * 🐛 Fix state not catching netboot properly This worked with the old cos-immutable-rootfs due to the rd.cos.disable stanza in cmdline making the livecd cloud config file trigger, which created the livecd sentinel file. With immucore, the sentinel is being created during initramfs directly so we need to rely on the cmdline to identify it. State sdk should know that having the netboot stanza should identify the boot as cdlive Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * Fix custom binds set as RW_PATHS RW_PATHS are meant for overlay dirs which go away after a reboot. Custom binds/binds are mounted under COS_PERSISTENT, so they persist after reboot AND are RW by default. This patch removes adding the custom binds into the RW_PATHS on the cos-layout file as that can lead to unintended consequences Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * Dont run custom mounts on livecd and recovery Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * Rework writing custom ephemeral/binds Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * Maybe fix tests Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * Add missing sgdisk to ubuntu images Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * 🐛 Backport dracut patch for ubuntu 20 iscsi There was an issue with dracut 48 in which the iscsid.socket required fs targets to be ready. On an iso this could lead to a dependency cycle between the dmsquash module setting up the livecd rootfs and the iscsi socket required the initrd-fs to be ready. This was fixed on dracut 50 and its what this patch brings, dropping the socket dependency on the fs target so it can break the dependency cycle. This only affect ubuntu 20 lts, and only affects booting from the iso. Alos the issue is random as systemd will decide to break the dependency in a non predictable way by disabling one of the services that conflict, so sometimes it would be the iscsi serviec, which would make the iso boot but sometimes it could be other more important services liek teh local fs or the dracut pre-mount services. Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> (cherry picked from commit 63f0c75d69ab3adca143f917c2e31b75ca3d96c7) * Bump immucore Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> --------- Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> Co-authored-by: Ettore Di Giacinto <mudler@users.noreply.github.com>
218 lines
5.6 KiB
Go
218 lines
5.6 KiB
Go
package state
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/itchyny/gojq"
|
|
"github.com/jaypipes/ghw"
|
|
"github.com/jaypipes/ghw/pkg/block"
|
|
"github.com/kairos-io/kairos/pkg/machine"
|
|
"github.com/kairos-io/kairos/pkg/utils"
|
|
"github.com/zcalusic/sysinfo"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const (
|
|
Active Boot = "active_boot"
|
|
Passive Boot = "passive_boot"
|
|
Recovery Boot = "recovery_boot"
|
|
LiveCD Boot = "livecd_boot"
|
|
Unknown Boot = "unknown"
|
|
)
|
|
|
|
type Boot string
|
|
|
|
type PartitionState struct {
|
|
Mounted bool `yaml:"mounted" json:"mounted"`
|
|
Name string `yaml:"name" json:"name"`
|
|
Label string `yaml:"label" json:"label"`
|
|
MountPoint string `yaml:"mount_point" json:"mount_point"`
|
|
SizeBytes uint64 `yaml:"size_bytes" json:"size_bytes"`
|
|
Type string `yaml:"type" json:"type"`
|
|
IsReadOnly bool `yaml:"read_only" json:"read_only"`
|
|
Found bool `yaml:"found" json:"found"`
|
|
UUID string `yaml:"uuid" json:"uuid"` // This would be volume UUID on macOS, PartUUID on linux, empty on Windows
|
|
}
|
|
|
|
type Kairos struct {
|
|
Flavor string `yaml:"flavor" json:"flavor"`
|
|
Version string `yaml:"version" json:"version"`
|
|
}
|
|
|
|
type Runtime struct {
|
|
UUID string `yaml:"uuid" json:"uuid"`
|
|
Persistent PartitionState `yaml:"persistent" json:"persistent"`
|
|
Recovery PartitionState `yaml:"recovery" json:"recovery"`
|
|
OEM PartitionState `yaml:"oem" json:"oem"`
|
|
State PartitionState `yaml:"state" json:"state"`
|
|
BootState Boot `yaml:"boot" json:"boot"`
|
|
System sysinfo.SysInfo `yaml:"system" json:"system"`
|
|
Kairos Kairos `yaml:"kairos" json:"kairos"`
|
|
}
|
|
|
|
type FndMnt struct {
|
|
Filesystems []struct {
|
|
Target string `json:"target,omitempty"`
|
|
FsOptions string `json:"fs-options,omitempty"`
|
|
} `json:"filesystems,omitempty"`
|
|
}
|
|
|
|
func detectPartition(b *block.Partition) PartitionState {
|
|
// If mountpoint seems empty, try to get the mountpoint of the partition label also the RO status
|
|
// This is a current shortcoming of ghw which only identifies mountpoints via device, not by label/uuid/anything else
|
|
mountpoint := b.MountPoint
|
|
readOnly := b.IsReadOnly
|
|
if b.MountPoint == "" && b.Label != "" {
|
|
out, err := utils.SH(fmt.Sprintf("findmnt /dev/disk/by-label/%s -f -J -o TARGET,FS-OPTIONS", b.Label))
|
|
fmt.Println(out)
|
|
mnt := &FndMnt{}
|
|
if err == nil {
|
|
err = json.Unmarshal([]byte(out), mnt)
|
|
// This should not happen, if there were no targets, the command would have returned an error, but you never know...
|
|
if err == nil && len(mnt.Filesystems) == 1 {
|
|
mountpoint = mnt.Filesystems[0].Target
|
|
// Don't assume its ro or rw by default, check both. One should match
|
|
regexRW := regexp.MustCompile("^rw,|^rw$|,rw,|,rw$")
|
|
regexRO := regexp.MustCompile("^ro,|^ro$|,ro,|,ro$")
|
|
if regexRW.Match([]byte(mnt.Filesystems[0].FsOptions)) {
|
|
readOnly = false
|
|
}
|
|
if regexRO.Match([]byte(mnt.Filesystems[0].FsOptions)) {
|
|
readOnly = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return PartitionState{
|
|
Type: b.Type,
|
|
IsReadOnly: readOnly,
|
|
UUID: b.UUID,
|
|
Name: fmt.Sprintf("/dev/%s", b.Name),
|
|
SizeBytes: b.SizeBytes,
|
|
Label: b.Label,
|
|
MountPoint: mountpoint,
|
|
Mounted: mountpoint != "",
|
|
Found: true,
|
|
}
|
|
}
|
|
|
|
func detectBoot() Boot {
|
|
cmdline, err := os.ReadFile("/proc/cmdline")
|
|
if err != nil {
|
|
return Unknown
|
|
}
|
|
cmdlineS := string(cmdline)
|
|
fmt.Println(cmdlineS)
|
|
switch {
|
|
case strings.Contains(cmdlineS, "COS_ACTIVE"):
|
|
return Active
|
|
case strings.Contains(cmdlineS, "COS_PASSIVE"):
|
|
return Passive
|
|
case strings.Contains(cmdlineS, "COS_RECOVERY"), strings.Contains(cmdlineS, "COS_SYSTEM"):
|
|
return Recovery
|
|
case strings.Contains(cmdlineS, "live:LABEL"), strings.Contains(cmdlineS, "live:CDLABEL"), strings.Contains(cmdlineS, "netboot"):
|
|
return LiveCD
|
|
default:
|
|
return Unknown
|
|
}
|
|
}
|
|
|
|
func detectRuntimeState(r *Runtime) error {
|
|
blockDevices, err := block.New(ghw.WithDisableTools(), ghw.WithDisableWarnings())
|
|
// ghw currently only detects if partitions are mounted via the device
|
|
// If we mount them via label, then its set as not mounted.
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, d := range blockDevices.Disks {
|
|
for _, part := range d.Partitions {
|
|
switch part.Label {
|
|
case "COS_PERSISTENT":
|
|
r.Persistent = detectPartition(part)
|
|
case "COS_RECOVERY":
|
|
r.Recovery = detectPartition(part)
|
|
case "COS_OEM":
|
|
r.OEM = detectPartition(part)
|
|
case "COS_STATE":
|
|
r.State = detectPartition(part)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func detectSystem(r *Runtime) {
|
|
var si sysinfo.SysInfo
|
|
|
|
si.GetSysInfo()
|
|
r.System = si
|
|
}
|
|
|
|
func detectKairos(r *Runtime) {
|
|
k := &Kairos{}
|
|
flavor, err := utils.OSRelease("FLAVOR")
|
|
if err == nil {
|
|
k.Flavor = flavor
|
|
}
|
|
v, err := utils.OSRelease("VERSION")
|
|
if err == nil {
|
|
k.Version = v
|
|
}
|
|
r.Kairos = *k
|
|
}
|
|
|
|
func NewRuntime() (Runtime, error) {
|
|
runtime := &Runtime{
|
|
BootState: detectBoot(),
|
|
UUID: machine.UUID(),
|
|
}
|
|
|
|
detectSystem(runtime)
|
|
detectKairos(runtime)
|
|
err := detectRuntimeState(runtime)
|
|
|
|
return *runtime, err
|
|
}
|
|
|
|
func (r Runtime) String() string {
|
|
dat, err := yaml.Marshal(r)
|
|
if err == nil {
|
|
return string(dat)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (r Runtime) Query(s string) (res string, err error) {
|
|
s = fmt.Sprintf(".%s", s)
|
|
jsondata := map[string]interface{}{}
|
|
var dat []byte
|
|
dat, err = json.Marshal(r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = json.Unmarshal(dat, &jsondata)
|
|
if err != nil {
|
|
return
|
|
}
|
|
query, err := gojq.Parse(s)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
iter := query.Run(jsondata) // or query.RunWithContext
|
|
for {
|
|
v, ok := iter.Next()
|
|
if !ok {
|
|
break
|
|
}
|
|
if err, ok := v.(error); ok {
|
|
return res, err
|
|
}
|
|
res += fmt.Sprint(v)
|
|
}
|
|
return
|
|
}
|