Files
kenaito-kopsc/internal/platform/platform.go
T
odboy bd6f20d7c5 feat: 新增 proc nohup 后台运行、docker save/load 内置实现,移除 Podman 支持
- proc.nohup:后台运行命令并将 stdout/stderr 追加写入日志,
  Unix 下新会话、Windows 下 DETACHED_PROCESS 脱离终端
- docker.save:等价 docker save <镜像> -o <镜像名>.tar,文件名按
  仓库名+标签生成(alpine:3.20 → alpine_3.20.tar),传镜像 ID 时
  先解析出对应名称;docker.load 改为归档文件作位置参数
- 移除 Podman 支持,所有容器命令只走 docker
2026-11-01 07:47:57 +08:00

119 lines
3.3 KiB
Go

// Package platform detects the execution environment that kopsc uses to pick
// the most specific command variant: OS/arch, Linux distribution and available
// container runtimes.
package platform
import (
"bufio"
"bytes"
"os"
"os/exec"
"runtime"
"strings"
)
// OSReleasePath is where Linux distributions publish their identity.
const OSReleasePath = "/etc/os-release"
// Info is the detected environment.
type Info struct {
OS string // runtime.GOOS, e.g. "linux", "darwin", "windows"
Arch string // runtime.GOARCH, e.g. "amd64", "arm64"
Distro string // lower-case os-release ID, empty when unknown
DistroLike []string // lower-case os-release ID_LIKE entries
Runtimes []string // container runtimes found on PATH, e.g. "docker"
}
// Probe gathers Info. Its dependencies are injectable so detection can be
// exercised without touching the real host.
type Probe struct {
GOOS string
GOARCH string
ReadFile func(string) ([]byte, error)
LookPath func(string) (string, error)
RuntimeCandidates []string
}
// System returns a Probe wired to the real process environment.
func System() Probe {
return Probe{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
ReadFile: os.ReadFile,
LookPath: exec.LookPath,
RuntimeCandidates: []string{"docker"},
}
}
// Detect inspects the environment. A missing or unreadable os-release file is
// not an error: it simply leaves Distro empty, which resolution treats as
// "unknown distribution".
func (p Probe) Detect() Info {
info := Info{OS: p.GOOS, Arch: p.GOARCH}
if p.GOOS == "linux" && p.ReadFile != nil {
if data, err := p.ReadFile(OSReleasePath); err == nil {
info.Distro, info.DistroLike = ParseOSRelease(data)
}
}
candidates := p.RuntimeCandidates
if candidates == nil {
candidates = []string{"docker"}
}
if p.LookPath != nil {
for _, name := range candidates {
if _, err := p.LookPath(name); err == nil {
info.Runtimes = append(info.Runtimes, name)
}
}
}
return info
}
// ParseOSRelease extracts ID and ID_LIKE from os-release content. Values are
// lower-cased and unquoted; parsing never fails, malformed lines are skipped.
func ParseOSRelease(data []byte) (distro string, like []string) {
values := make(map[string]string)
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok {
continue
}
values[strings.TrimSpace(key)] = strings.Trim(strings.TrimSpace(value), `"'`)
}
distro = strings.ToLower(strings.TrimSpace(values["ID"]))
for _, field := range strings.Fields(values["ID_LIKE"]) {
like = append(like, strings.ToLower(field))
}
return distro, like
}
// String renders Info for `kopsc platform`.
func (i Info) String() string {
var b strings.Builder
b.WriteString("os: " + i.OS + "\n")
b.WriteString("arch: " + i.Arch + "\n")
distro := i.Distro
if distro == "" {
distro = "-"
}
b.WriteString("distro: " + distro + "\n")
if len(i.DistroLike) > 0 {
b.WriteString("like: " + strings.Join(i.DistroLike, " ") + "\n")
}
runtimes := "-"
if len(i.Runtimes) > 0 {
runtimes = strings.Join(i.Runtimes, ", ")
}
b.WriteString("runtimes: " + runtimes + "\n")
return b.String()
}