| package agent
|
|
|
| import (
|
| "context"
|
| "log/slog"
|
| "os"
|
| "path/filepath"
|
| "runtime"
|
| "strings"
|
| "time"
|
|
|
| "github.com/henrygd/beszel/agent/utils"
|
| "github.com/henrygd/beszel/internal/entities/system"
|
|
|
| "github.com/shirou/gopsutil/v4/disk"
|
| )
|
|
|
|
|
|
|
| type fsRegistrationContext struct {
|
| filesystem string
|
| isWindows bool
|
| efPath string
|
| diskIoCounters map[string]disk.IOCountersStat
|
| }
|
|
|
|
|
|
|
| type diskDiscovery struct {
|
| agent *Agent
|
| rootMountPoint string
|
| partitions []disk.PartitionStat
|
| usageFn func(string) (*disk.UsageStat, error)
|
| ctx fsRegistrationContext
|
| }
|
|
|
|
|
| type prevDisk struct {
|
| readBytes uint64
|
| writeBytes uint64
|
| readTime uint64
|
| writeTime uint64
|
| ioTime uint64
|
| weightedIO uint64
|
| readCount uint64
|
| writeCount uint64
|
| at time.Time
|
| }
|
|
|
|
|
| func prevDiskFromCounter(d disk.IOCountersStat, t time.Time) prevDisk {
|
| return prevDisk{
|
| readBytes: d.ReadBytes,
|
| writeBytes: d.WriteBytes,
|
| readTime: d.ReadTime,
|
| writeTime: d.WriteTime,
|
| ioTime: d.IoTime,
|
| weightedIO: d.WeightedIO,
|
| readCount: d.ReadCount,
|
| writeCount: d.WriteCount,
|
| at: t,
|
| }
|
| }
|
|
|
|
|
|
|
| func parseFilesystemEntry(entry string) (device, customName string) {
|
| entry = strings.TrimSpace(entry)
|
| if parts := strings.SplitN(entry, "__", 2); len(parts) == 2 {
|
| device = strings.TrimSpace(parts[0])
|
| customName = strings.TrimSpace(parts[1])
|
| } else {
|
| device = entry
|
| }
|
| return device, customName
|
| }
|
|
|
|
|
|
|
|
|
| func extraFilesystemPartitionInfo(p disk.PartitionStat) (device, customName string) {
|
| device = strings.TrimSpace(p.Device)
|
| folderDevice, customName := parseFilesystemEntry(filepath.Base(p.Mountpoint))
|
| if device == "" {
|
| device = folderDevice
|
| }
|
| return device, customName
|
| }
|
|
|
| func isDockerSpecialMountpoint(mountpoint string) bool {
|
| switch mountpoint {
|
| case "/etc/hosts", "/etc/resolv.conf", "/etc/hostname":
|
| return true
|
| }
|
| return false
|
| }
|
|
|
|
|
|
|
| func registerFilesystemStats(existing map[string]*system.FsStats, device, mountpoint string, root bool, customName string, ctx fsRegistrationContext) (string, *system.FsStats, bool) {
|
| key := device
|
| if !ctx.isWindows {
|
| key = filepath.Base(device)
|
| }
|
|
|
| if root {
|
|
|
|
|
|
|
| if _, ioMatch := ctx.diskIoCounters[key]; !ioMatch {
|
| if matchedKey, match := findIoDevice(key, ctx.diskIoCounters); match {
|
| key = matchedKey
|
| } else if ctx.filesystem != "" {
|
| if matchedKey, match := findIoDevice(ctx.filesystem, ctx.diskIoCounters); match {
|
| key = matchedKey
|
| }
|
| }
|
| if _, ioMatch = ctx.diskIoCounters[key]; !ioMatch {
|
| slog.Warn("Root I/O unmapped; set FILESYSTEM", "device", device, "mountpoint", mountpoint)
|
| }
|
| }
|
| } else {
|
|
|
|
|
|
|
| if _, ioMatch := ctx.diskIoCounters[key]; !ioMatch {
|
| if strings.HasPrefix(mountpoint, ctx.efPath) {
|
| folderDevice, _ := parseFilesystemEntry(filepath.Base(mountpoint))
|
| if folderDevice != "" {
|
| if matchedKey, match := findIoDevice(folderDevice, ctx.diskIoCounters); match {
|
| key = matchedKey
|
| }
|
| }
|
| }
|
| if _, ioMatch = ctx.diskIoCounters[key]; !ioMatch {
|
| if matchedKey, match := findIoDevice(key, ctx.diskIoCounters); match {
|
| key = matchedKey
|
| }
|
| }
|
| }
|
| }
|
|
|
| if _, exists := existing[key]; exists {
|
| return "", nil, false
|
| }
|
|
|
| fsStats := &system.FsStats{Root: root, Mountpoint: mountpoint}
|
| if customName != "" {
|
| fsStats.Name = customName
|
| }
|
| return key, fsStats, true
|
| }
|
|
|
|
|
|
|
|
|
| func (d *diskDiscovery) addFsStat(device, mountpoint string, root bool, customName string) {
|
| key, fsStats, ok := registerFilesystemStats(d.agent.fsStats, device, mountpoint, root, customName, d.ctx)
|
| if !ok {
|
| return
|
| }
|
| d.agent.fsStats[key] = fsStats
|
| name := key
|
| if customName != "" {
|
| name = customName
|
| }
|
| slog.Info("Detected disk", "name", name, "device", device, "mount", mountpoint, "io", key, "root", root)
|
| }
|
|
|
|
|
|
|
|
|
| func (d *diskDiscovery) addConfiguredRootFs() bool {
|
| if d.ctx.filesystem == "" {
|
| return false
|
| }
|
|
|
| for _, p := range d.partitions {
|
| if filesystemMatchesPartitionSetting(d.ctx.filesystem, p) {
|
| d.addFsStat(p.Device, p.Mountpoint, true, "")
|
| return true
|
| }
|
| }
|
|
|
|
|
|
|
| if ioKey, match := findIoDevice(d.ctx.filesystem, d.ctx.diskIoCounters); match {
|
| d.agent.fsStats[ioKey] = &system.FsStats{Root: true, Mountpoint: d.rootMountPoint}
|
| return true
|
| }
|
|
|
| slog.Warn("Partition details not found", "filesystem", d.ctx.filesystem)
|
| return false
|
| }
|
|
|
| func isRootFallbackPartition(p disk.PartitionStat, rootMountPoint string) bool {
|
| return p.Mountpoint == rootMountPoint ||
|
| (isDockerSpecialMountpoint(p.Mountpoint) && strings.HasPrefix(p.Device, "/dev"))
|
| }
|
|
|
|
|
|
|
|
|
| func (d *diskDiscovery) addPartitionRootFs(device, mountpoint string) bool {
|
| fs, match := findIoDevice(filepath.Base(device), d.ctx.diskIoCounters)
|
| if !match {
|
| return false
|
| }
|
|
|
|
|
| d.addFsStat(fs, mountpoint, true, "")
|
| return true
|
| }
|
|
|
|
|
|
|
|
|
| func (d *diskDiscovery) addLastResortRootFs() {
|
| rootKey := mostActiveIoDevice(d.ctx.diskIoCounters)
|
| if rootKey != "" {
|
| slog.Warn("Using most active device for root I/O; set FILESYSTEM to override", "device", rootKey)
|
| } else {
|
| rootKey = filepath.Base(d.rootMountPoint)
|
| if _, exists := d.agent.fsStats[rootKey]; exists {
|
| rootKey = "root"
|
| }
|
| slog.Warn("Root I/O device not detected; set FILESYSTEM to override")
|
| }
|
| d.agent.fsStats[rootKey] = &system.FsStats{Root: true, Mountpoint: d.rootMountPoint}
|
| }
|
|
|
|
|
|
|
| func findPartitionByFilesystemSetting(filesystem string, partitions []disk.PartitionStat) (disk.PartitionStat, bool) {
|
| for _, p := range partitions {
|
| if strings.HasSuffix(p.Device, filesystem) || p.Mountpoint == filesystem {
|
| return p, true
|
| }
|
| }
|
| return disk.PartitionStat{}, false
|
| }
|
|
|
|
|
|
|
| func (d *diskDiscovery) addConfiguredExtraFsEntry(filesystem, customName string) {
|
| if p, found := findPartitionByFilesystemSetting(filesystem, d.partitions); found {
|
| d.addFsStat(p.Device, p.Mountpoint, false, customName)
|
| return
|
| }
|
|
|
| if _, err := d.usageFn(filesystem); err == nil {
|
| d.addFsStat(filepath.Base(filesystem), filesystem, false, customName)
|
| return
|
| } else {
|
| slog.Error("Invalid filesystem", "name", filesystem, "err", err)
|
| }
|
| }
|
|
|
|
|
|
|
| func (d *diskDiscovery) addConfiguredExtraFilesystems(extraFilesystems string) {
|
| for fsEntry := range strings.SplitSeq(extraFilesystems, ",") {
|
| filesystem, customName := parseFilesystemEntry(fsEntry)
|
| d.addConfiguredExtraFsEntry(filesystem, customName)
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| func (d *diskDiscovery) addPartitionExtraFs(p disk.PartitionStat) {
|
| if filepath.Dir(p.Mountpoint) != d.ctx.efPath {
|
| return
|
| }
|
| device, customName := extraFilesystemPartitionInfo(p)
|
| d.addFsStat(device, p.Mountpoint, false, customName)
|
| }
|
|
|
|
|
|
|
|
|
| func (d *diskDiscovery) addExtraFilesystemFolders(folderNames []string) {
|
| existingMountpoints := make(map[string]bool, len(d.agent.fsStats))
|
| for _, stats := range d.agent.fsStats {
|
| existingMountpoints[stats.Mountpoint] = true
|
| }
|
|
|
| for _, folderName := range folderNames {
|
| mountpoint := filepath.Join(d.ctx.efPath, folderName)
|
| slog.Debug("/extra-filesystems", "mountpoint", mountpoint)
|
| if existingMountpoints[mountpoint] {
|
| continue
|
| }
|
| device, customName := parseFilesystemEntry(folderName)
|
| d.addFsStat(device, mountpoint, false, customName)
|
| }
|
| }
|
|
|
|
|
| func (a *Agent) initializeDiskInfo() {
|
| filesystem, _ := utils.GetEnv("FILESYSTEM")
|
| hasRoot := false
|
| isWindows := runtime.GOOS == "windows"
|
|
|
| partitions, err := disk.PartitionsWithContext(context.Background(), true)
|
| if err != nil {
|
| slog.Error("Error getting disk partitions", "err", err)
|
| }
|
| slog.Debug("Disk", "partitions", partitions)
|
|
|
|
|
| if isWindows {
|
| for i, p := range partitions {
|
| partitions[i].Device = strings.TrimSuffix(p.Device, "\\")
|
| }
|
| }
|
|
|
| diskIoCounters, err := disk.IOCounters()
|
| if err != nil {
|
| slog.Error("Error getting diskstats", "err", err)
|
| }
|
| slog.Debug("Disk I/O", "diskstats", diskIoCounters)
|
| ctx := fsRegistrationContext{
|
| filesystem: filesystem,
|
| isWindows: isWindows,
|
| diskIoCounters: diskIoCounters,
|
| efPath: "/extra-filesystems",
|
| }
|
|
|
|
|
| discovery := diskDiscovery{
|
| agent: a,
|
| rootMountPoint: a.getRootMountPoint(),
|
| partitions: partitions,
|
| usageFn: disk.Usage,
|
| ctx: ctx,
|
| }
|
|
|
| hasRoot = discovery.addConfiguredRootFs()
|
|
|
|
|
| if extraFilesystems, exists := utils.GetEnv("EXTRA_FILESYSTEMS"); exists {
|
| discovery.addConfiguredExtraFilesystems(extraFilesystems)
|
| }
|
|
|
|
|
| for _, p := range partitions {
|
| if !hasRoot && isRootFallbackPartition(p, discovery.rootMountPoint) {
|
| hasRoot = discovery.addPartitionRootFs(p.Device, p.Mountpoint)
|
| }
|
| discovery.addPartitionExtraFs(p)
|
| }
|
|
|
|
|
| if folders, err := os.ReadDir(discovery.ctx.efPath); err == nil {
|
| folderNames := make([]string, 0, len(folders))
|
| for _, folder := range folders {
|
| if folder.IsDir() {
|
| folderNames = append(folderNames, folder.Name())
|
| }
|
| }
|
| discovery.addExtraFilesystemFolders(folderNames)
|
| }
|
|
|
|
|
|
|
| if !hasRoot {
|
| discovery.addLastResortRootFs()
|
| }
|
|
|
| a.pruneDuplicateRootExtraFilesystems()
|
| a.initializeDiskIoStats(diskIoCounters)
|
| }
|
|
|
|
|
| func (a *Agent) pruneDuplicateRootExtraFilesystems() {
|
| var rootMountpoint string
|
| for _, stats := range a.fsStats {
|
| if stats != nil && stats.Root {
|
| rootMountpoint = stats.Mountpoint
|
| break
|
| }
|
| }
|
| if rootMountpoint == "" {
|
| return
|
| }
|
| rootUsage, err := disk.Usage(rootMountpoint)
|
| if err != nil {
|
| return
|
| }
|
| for name, stats := range a.fsStats {
|
| if stats == nil || stats.Root {
|
| continue
|
| }
|
| extraUsage, err := disk.Usage(stats.Mountpoint)
|
| if err != nil {
|
| continue
|
| }
|
| if hasSameDiskUsage(rootUsage, extraUsage) {
|
| slog.Info("Ignoring duplicate FS", "name", name, "mount", stats.Mountpoint)
|
| delete(a.fsStats, name)
|
| }
|
| }
|
| }
|
|
|
|
|
| func hasSameDiskUsage(a, b *disk.UsageStat) bool {
|
| if a == nil || b == nil || a.Total == 0 || b.Total == 0 {
|
| return false
|
| }
|
|
|
| const toleranceBytes uint64 = 16 * 1024 * 1024
|
| return withinUsageTolerance(a.Total, b.Total, toleranceBytes) &&
|
| withinUsageTolerance(a.Used, b.Used, toleranceBytes)
|
| }
|
|
|
|
|
| func withinUsageTolerance(a, b, tolerance uint64) bool {
|
| if a >= b {
|
| return a-b <= tolerance
|
| }
|
| return b-a <= tolerance
|
| }
|
|
|
| type ioMatchCandidate struct {
|
| name string
|
| bytes uint64
|
| ops uint64
|
| }
|
|
|
|
|
|
|
| func findIoDevice(filesystem string, diskIoCounters map[string]disk.IOCountersStat) (string, bool) {
|
| filesystem = normalizeDeviceName(filesystem)
|
| if filesystem == "" {
|
| return "", false
|
| }
|
|
|
| candidates := []ioMatchCandidate{}
|
|
|
| for _, d := range diskIoCounters {
|
| if normalizeDeviceName(d.Name) == filesystem || (d.Label != "" && normalizeDeviceName(d.Label) == filesystem) {
|
| return d.Name, true
|
| }
|
| if prefixRelated(normalizeDeviceName(d.Name), filesystem) ||
|
| (d.Label != "" && prefixRelated(normalizeDeviceName(d.Label), filesystem)) {
|
| candidates = append(candidates, ioMatchCandidate{
|
| name: d.Name,
|
| bytes: d.ReadBytes + d.WriteBytes,
|
| ops: d.ReadCount + d.WriteCount,
|
| })
|
| }
|
| }
|
|
|
| if len(candidates) == 0 {
|
| return "", false
|
| }
|
|
|
| best := candidates[0]
|
| for _, c := range candidates[1:] {
|
| if c.bytes > best.bytes ||
|
| (c.bytes == best.bytes && c.ops > best.ops) ||
|
| (c.bytes == best.bytes && c.ops == best.ops && c.name < best.name) {
|
| best = c
|
| }
|
| }
|
|
|
| slog.Info("Using disk I/O fallback", "requested", filesystem, "selected", best.name)
|
| return best.name, true
|
| }
|
|
|
|
|
|
|
| func mostActiveIoDevice(diskIoCounters map[string]disk.IOCountersStat) string {
|
| var best ioMatchCandidate
|
| for _, d := range diskIoCounters {
|
| c := ioMatchCandidate{
|
| name: d.Name,
|
| bytes: d.ReadBytes + d.WriteBytes,
|
| ops: d.ReadCount + d.WriteCount,
|
| }
|
| if best.name == "" || c.bytes > best.bytes ||
|
| (c.bytes == best.bytes && c.ops > best.ops) ||
|
| (c.bytes == best.bytes && c.ops == best.ops && c.name < best.name) {
|
| best = c
|
| }
|
| }
|
| return best.name
|
| }
|
|
|
|
|
| func prefixRelated(a, b string) bool {
|
| if a == "" || b == "" || a == b {
|
| return false
|
| }
|
| return strings.HasPrefix(a, b) || strings.HasPrefix(b, a)
|
| }
|
|
|
|
|
|
|
|
|
| func filesystemMatchesPartitionSetting(filesystem string, p disk.PartitionStat) bool {
|
| filesystem = strings.TrimSpace(filesystem)
|
| if filesystem == "" {
|
| return false
|
| }
|
| if p.Mountpoint == filesystem {
|
| return true
|
| }
|
|
|
| fsName := normalizeDeviceName(filesystem)
|
| partName := normalizeDeviceName(p.Device)
|
| if fsName == "" || partName == "" {
|
| return false
|
| }
|
| if fsName == partName {
|
| return true
|
| }
|
| return prefixRelated(partName, fsName)
|
| }
|
|
|
|
|
| func normalizeDeviceName(value string) string {
|
| name := filepath.Base(strings.TrimSpace(value))
|
| if name == "." {
|
| return ""
|
| }
|
| return name
|
| }
|
|
|
|
|
| func (a *Agent) initializeDiskIoStats(diskIoCounters map[string]disk.IOCountersStat) {
|
| a.fsNames = a.fsNames[:0]
|
| now := time.Now()
|
| for device, stats := range a.fsStats {
|
|
|
| d, exists := diskIoCounters[device]
|
| if !exists {
|
| slog.Warn("Device not found in diskstats", "name", device)
|
| continue
|
| }
|
|
|
| stats.Time = now
|
| stats.TotalRead = d.ReadBytes
|
| stats.TotalWrite = d.WriteBytes
|
|
|
| a.fsNames = append(a.fsNames, device)
|
| }
|
| }
|
|
|
|
|
| func (a *Agent) updateDiskUsage(systemStats *system.Stats) {
|
|
|
|
|
|
|
| cacheExtraFs := a.diskUsageCacheDuration > 0 &&
|
| !a.lastDiskUsageUpdate.IsZero() &&
|
| time.Since(a.lastDiskUsageUpdate) < a.diskUsageCacheDuration
|
|
|
|
|
| for _, stats := range a.fsStats {
|
|
|
| if cacheExtraFs && !stats.Root {
|
| continue
|
| }
|
| if d, err := disk.Usage(stats.Mountpoint); err == nil {
|
| stats.DiskTotal = utils.BytesToGigabytes(d.Total)
|
| stats.DiskUsed = utils.BytesToGigabytes(d.Used)
|
| if stats.Root {
|
| systemStats.DiskTotal = utils.BytesToGigabytes(d.Total)
|
| systemStats.DiskUsed = utils.BytesToGigabytes(d.Used)
|
| systemStats.DiskPct = utils.TwoDecimals(d.UsedPercent)
|
| }
|
| } else {
|
|
|
| slog.Error("Error getting disk stats", "name", stats.Mountpoint, "err", err)
|
| stats.DiskTotal = 0
|
| stats.DiskUsed = 0
|
| stats.TotalRead = 0
|
| stats.TotalWrite = 0
|
| }
|
| }
|
|
|
|
|
| if !cacheExtraFs {
|
| a.lastDiskUsageUpdate = time.Now()
|
| }
|
| }
|
|
|
|
|
| func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
|
|
|
| if ioCounters, err := disk.IOCounters(a.fsNames...); err == nil {
|
|
|
| if _, ok := a.diskPrev[cacheTimeMs]; !ok {
|
| a.diskPrev[cacheTimeMs] = make(map[string]prevDisk)
|
| }
|
| now := time.Now()
|
| for name, d := range ioCounters {
|
| stats := a.fsStats[d.Name]
|
| if stats == nil {
|
|
|
| continue
|
| }
|
|
|
|
|
| prev, hasPrev := a.diskPrev[cacheTimeMs][name]
|
| if !hasPrev {
|
|
|
| prev = prevDisk{
|
| readBytes: stats.TotalRead,
|
| writeBytes: stats.TotalWrite,
|
| readTime: d.ReadTime,
|
| writeTime: d.WriteTime,
|
| ioTime: d.IoTime,
|
| weightedIO: d.WeightedIO,
|
| readCount: d.ReadCount,
|
| writeCount: d.WriteCount,
|
| at: stats.Time,
|
| }
|
| if prev.at.IsZero() {
|
| prev = prevDiskFromCounter(d, now)
|
| }
|
| }
|
|
|
| msElapsed := uint64(now.Sub(prev.at).Milliseconds())
|
|
|
|
|
| a.diskPrev[cacheTimeMs][name] = prevDiskFromCounter(d, now)
|
|
|
|
|
| if msElapsed < 100 {
|
| continue
|
| }
|
|
|
| diskIORead := (d.ReadBytes - prev.readBytes) * 1000 / msElapsed
|
| diskIOWrite := (d.WriteBytes - prev.writeBytes) * 1000 / msElapsed
|
| readMbPerSecond := utils.BytesToMegabytes(float64(diskIORead))
|
| writeMbPerSecond := utils.BytesToMegabytes(float64(diskIOWrite))
|
|
|
|
|
| if readMbPerSecond > 50_000 || writeMbPerSecond > 50_000 {
|
| slog.Warn("Invalid disk I/O. Resetting.", "name", d.Name, "read", readMbPerSecond, "write", writeMbPerSecond)
|
|
|
| a.initializeDiskIoStats(ioCounters)
|
| continue
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| diskReadTime := utils.TwoDecimals(float64(d.ReadTime-prev.readTime) / float64(msElapsed) * 100)
|
| diskWriteTime := utils.TwoDecimals(float64(d.WriteTime-prev.writeTime) / float64(msElapsed) * 100)
|
|
|
|
|
| diskIoUtilPct := utils.TwoDecimals(float64(d.IoTime-prev.ioTime) / float64(msElapsed) * 100)
|
|
|
|
|
|
|
|
|
| diskWeightedIO := utils.TwoDecimals(float64(d.WeightedIO-prev.weightedIO) / float64(msElapsed) * 100)
|
|
|
|
|
|
|
| var rAwait, wAwait float64
|
| if deltaReadCount := d.ReadCount - prev.readCount; deltaReadCount > 0 {
|
| rAwait = utils.TwoDecimals(float64(d.ReadTime-prev.readTime) / float64(deltaReadCount))
|
| }
|
| if deltaWriteCount := d.WriteCount - prev.writeCount; deltaWriteCount > 0 {
|
| wAwait = utils.TwoDecimals(float64(d.WriteTime-prev.writeTime) / float64(deltaWriteCount))
|
| }
|
|
|
|
|
| stats.Time = now
|
| stats.TotalRead = d.ReadBytes
|
| stats.TotalWrite = d.WriteBytes
|
| stats.DiskReadPs = readMbPerSecond
|
| stats.DiskWritePs = writeMbPerSecond
|
| stats.DiskReadBytes = diskIORead
|
| stats.DiskWriteBytes = diskIOWrite
|
| stats.DiskIoStats[0] = diskReadTime
|
| stats.DiskIoStats[1] = diskWriteTime
|
| stats.DiskIoStats[2] = diskIoUtilPct
|
| stats.DiskIoStats[3] = rAwait
|
| stats.DiskIoStats[4] = wAwait
|
| stats.DiskIoStats[5] = diskWeightedIO
|
|
|
| if stats.Root {
|
| systemStats.DiskReadPs = stats.DiskReadPs
|
| systemStats.DiskWritePs = stats.DiskWritePs
|
| systemStats.DiskIO[0] = diskIORead
|
| systemStats.DiskIO[1] = diskIOWrite
|
| systemStats.DiskIoStats[0] = diskReadTime
|
| systemStats.DiskIoStats[1] = diskWriteTime
|
| systemStats.DiskIoStats[2] = diskIoUtilPct
|
| systemStats.DiskIoStats[3] = rAwait
|
| systemStats.DiskIoStats[4] = wAwait
|
| systemStats.DiskIoStats[5] = diskWeightedIO
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| func (a *Agent) getRootMountPoint() string {
|
| if runtime.GOOS == "windows" {
|
| if sd := os.Getenv("SystemDrive"); sd != "" {
|
| return sd
|
| }
|
| return "C:"
|
| }
|
|
|
|
|
| if osReleaseContent, err := os.ReadFile("/etc/os-release"); err == nil {
|
| content := string(osReleaseContent)
|
| if strings.Contains(content, "fedora") && strings.Contains(content, "silverblue") ||
|
| strings.Contains(content, "coreos") ||
|
| strings.Contains(content, "flatcar") ||
|
| strings.Contains(content, "rhel-atomic") ||
|
| strings.Contains(content, "centos-atomic") {
|
|
|
| if _, err := os.Stat("/sysroot"); err == nil {
|
| return "/sysroot"
|
| }
|
| }
|
| }
|
|
|
|
|
| if _, err := os.Stat("/run/ostree"); err == nil {
|
|
|
| if _, err := os.Stat("/sysroot"); err == nil {
|
| return "/sysroot"
|
| }
|
| }
|
|
|
| return "/"
|
| }
|
|
|