staros/README.md
starainrt d93a851d1b
feat: 完善 staros 系统能力并更新 wincmd 发布版依赖
- 重构 sysconf 为文档模型 INI Parser 与 Config Framework
- 强化 hosts 解析、插入校验、写回与异常输入处理
- 完善 StarCmd 生命周期、等待 API、流式输出与 IO 重定向
- 扩展跨平台文件时间、文件锁、内存、进程与网络能力
- 将 Windows 进程适配更新到 b612.me/wincmd v0.1.0
- 移除本地 wincmd/win32api replace,改用发布版依赖
- 将最低 Go 版本提升到 1.18
- 补充 hosts、sysconf、FileLock、StarCmd 与平台适配回归测试
2026-06-09 18:10:19 +08:00

120 lines
9.9 KiB
Markdown

# staros
`staros` is a cgo-free Go package for small cross-platform OS utilities.
The package keeps compatibility with existing APIs, but platform-dependent functions should prefer explicit error-returning variants where available. Unsupported platform implementations return `ERR_UNSUPPORTED` instead of silently pretending to work.
## Go Version
`go.mod` declares `go 1.18`. This release targets Go 1.18 or newer and no longer promises Go 1.16/1.17 compatibility. The release gate includes the current local Go toolchain plus cross-platform compile checks.
## Platform Support
| Area | Linux | Windows | Darwin |
| --- | --- | --- | --- |
| Basic path checks: `Exists`, `IsFile`, `IsFolder` | Supported | Supported | Supported |
| File locks: `FileLock` | `flock` | Win32 lock API | `flock` |
| File timestamps: `GetFileCreationTime`, `GetFileAccessTime`, `SetFileTimesE` | ctime/access/modtime via `x/sys/unix` | creation/access/modtime via `x/sys/windows` | birth/access/modtime via stdlib/syscall |
| Memory: `Memory` | `/proc` and `syscall.Sysinfo` | Win32 `GlobalMemoryStatusEx` | `vm_stat` and `sysctl` |
| Disk: `DiskUsageE` | `Statfs` | `GetDiskFreeSpaceExW` | `Statfs` |
| OS identity: `IsRoot`, `Whoami` | Supported | `IsRoot` supported, `Whoami` returns `ERR_UNSUPPORTED` | Supported |
| CPU usage: `CpuUsage`, `CpuUsageByPid` | `/proc/stat` and `/proc/<pid>/stat` | Stub returns `0` | Stub returns `0` |
| Process query: `FindProcess*` | `/proc` backed | `wincmd` backed basic fields | Returns `ERR_UNSUPPORTED` |
| Process launch: `Command`, `CommandContext`, `Start` | Supported | Supported | Supported |
| Process lifecycle: `ReleaseE` | Starts through normal lifecycle with `Setsid` | Starts through normal lifecycle | Returns `ERR_UNSUPPORTED` |
| Process detach: `DetachE` | Start-before-Start only, then `Process.Release` | Start-before-Start only, then `Process.Release` | Returns `ERR_UNSUPPORTED` |
| Run as user: `SetRunUserE`, `DaemonWithUser` | Supported | Returns `ERR_UNSUPPORTED` | Returns `ERR_UNSUPPORTED` |
| Keep capabilities: `SetKeepCaps`, `StarCmd.SetKeepCaps` | Package helper uses Linux `prctl`; command helper preserves current caps via `AmbientCaps` | Returns `ERR_UNSUPPORTED` | Returns `ERR_UNSUPPORTED` |
| Network adapters/speeds/connections | `/proc/net` backed | IP Helper adapter counters and TCP/UDP owner-pid tables; no Unix socket/inode fields | Returns `ERR_UNSUPPORTED` |
| Beep | PC speaker or terminal bell fallback | Win32 `Beep` | `osascript` or terminal bell fallback |
## API Semantics
- `ERR_UNSUPPORTED` means the symbol exists for build compatibility but the current OS implementation is intentionally unavailable.
- `ReleaseE` preserves `StarCmd` lifecycle observation: `Stopped()` still closes after the process exits and `ExitCode()` is populated when available. The historical misspelled `Stoped()` method remains as a deprecated compatibility alias.
- `DetachE` is a true detached start path and must be called before `Start`; calling it after `Start` returns an already-started error to avoid racing `Process.Release()` with the internal `Wait()`.
- `Wait`, `WaitContext`, and `WaitTimeout` provide explicit lifecycle wait helpers that return the final process wait error. `Stopped()` remains available when callers only need a close signal.
- On Linux, the package-level `SetKeepCaps()` helper applies `prctl(PR_SET_KEEPCAPS)` to the current process. `StarCmd.SetKeepCaps()` is different: it snapshots the current capability set and configures the child command's `SysProcAttr.AmbientCaps` before `Start`.
- `StdoutChan`, `StderrChan`, and `OutputChan` provide best-effort streaming observation for future output chunks. They close with `Stopped()` and do not replace the existing full-output capture methods.
- `RedirectStdout`, `RedirectStderr`, `RedirectOutput`, and `RedirectStdin` configure process IO before `Start`. File helpers such as `RedirectStdoutFile` and `RedirectStdinFile` open the file and close it after the process reaches its final state.
- `WriteStdinE` and `WriteStdinStringE` write raw stdin data without appending a newline. `WriteStdinLineE` and the legacy `WriteCmdE` append one newline.
- Legacy methods that do not return errors are kept for compatibility; prefer the `*E` variants for new code.
## sysconf Migration
The `sysconf` package now uses a document-backed INI model instead of the old `SysConf` struct shape. This is a breaking API change: callers should migrate to `NewIni`, `NewLinuxConf`, `Document`, `Section`, and `Entry` directly instead of relying on a field-compatible wrapper.
Minimal migration rules:
- Replace parser setup through `SysConf` fields with constructors: use `sysconf.NewIni()` for sectioned INI files, or `sysconf.NewLinuxConf(equal)` for flat Linux-style config files.
- Replace direct segment/key data mutation with section methods: `ini.Section(name)`, `ini.Set(section, key, value)`, `sec.Set`, `sec.SetAll`, `sec.AddValue`, `sec.Delete`, and `ini.DeleteSection`.
- Replace single-value assumptions with duplicate-aware reads where needed: `ini.Get`/`sec.Get` returns the first value, while `ini.GetAll`/`sec.GetAll` returns all repeated keys.
- Replace manual struct binding with `ini.Unmarshal(&dst)` and `ini.Marshal(src)` using `seg` and `key` tags.
- Use `ini.Build()` or `ini.Save(path)` for write-back. Unchanged parsed lines keep their original formatting, while changed entries are rebuilt from the new model.
Example:
```go
ini := sysconf.NewIni()
if err := ini.Parse(data); err != nil {
return err
}
app := ini.Section("app")
if app == nil {
app = ini.AddSection("app")
}
_ = app.SetInt("port", 9090, "")
_ = app.SetAll("feature", []string{"stable", "audit"}, "")
out := ini.Build()
```
INI parser capabilities:
- `NewIni()` parses common sectioned INI files with `=` and `:` key/value delimiters, `#` and `;` comments, section header comments, quoted values, no-value keys, duplicate keys, duplicate sections, and backslash line continuation.
- Inline comments require whitespace before the comment marker, so values such as URLs or fragments containing `#` are not truncated accidentally.
- `NewIniWithProfiles(...)`, `StrictINIProfile()`, and `LinuxConfProfile(equal)` let callers pin parser behavior for strict sectioned INI or flat Linux-style config files without mutating parser fields ad hoc.
- `Document.Strict` can be enabled when callers want malformed input to return a `ParseError` with line and column information instead of preserving unknown lines as raw content.
- Write-back is lossless for unchanged parsed lines. Changed values that would otherwise be misread as comments, leading/trailing whitespace, tabs, or newlines are emitted as quoted values.
Config framework capabilities:
- `sysconf.NewConfig()`, `sysconf.LoadConfig(&dst, files, ...)`, and `sysconf.LoadConfigSources(&dst, sources, ...)` load one or more INI sources in order; later sources override earlier values for the same section/key.
- `sysconf.RequiredFile(path)` and `sysconf.OptionalFile(path)` declare whether a missing file should fail loading or be skipped. `sysconf.BytesSource(name, data)` and `sysconf.StringSource(name, data)` support in-memory overlays for tests, generated defaults, and embedded configs.
- `Config` exposes direct access and write-back helpers: `Get`, `GetAll`, error-returning typed getters such as `GetIntE` / `GetBoolE` / `GetDurationE`, `Has`, `Set`, `SetAll`, `Delete`, `Build`, `Save`, and `SaveAtomic`.
- Struct binding uses `seg`, `key`, `default`, `env`, `split`, and `required` tags. Nested structs inherit their parent `seg` tag, while `env:"-"` disables environment overrides for a field.
- Environment overrides are opt-in through `WithEnvPrefix` / `WithEnvLookup`; generated names normalize section and key names to uppercase underscore form, such as `APP_SERVER_PORT`.
- Binding supports strings, bools, signed/unsigned integers, floats, `time.Duration`, `encoding.TextUnmarshaler`, slices, arrays, and `map[string]T` for scalar `T`.
- Repeated INI keys bind naturally to slices. When a single value should expand into multiple collection items, add an explicit `split` tag such as `split:","`, `split:"|"`, or `split:"csv"`.
- `Config.SetStruct(src)` writes a config struct back into the current document, using repeated keys for slices/arrays and sorted `key=value` repeated entries for maps.
- `sysconf.DescribeConfig(src)` exports struct tag metadata as `ConfigFieldInfo` records, and `sysconf.SampleConfig(src)` builds a sample INI from defaults, current struct values, required placeholders, or type zero values without mutating the source struct.
- `Config.SectionNames()`, `Config.Keys(section)`, `Config.Flatten()`, and `Config.FlattenEntries()` expose sorted section/key discovery, duplicate-aware flattened values, and structured section/key/value entries for diagnostics, tests, and lightweight config export.
- `ConfigError` and `ConfigSourceError` include structured metadata and unwrap their underlying parse, file, or conversion errors for `errors.Is` / `errors.As`.
- A config struct can implement `Validate() error`; validation runs after defaults, file values, env overrides, required checks, and type binding.
Example:
```go
type AppConfig struct {
App struct {
Name string `key:"name" required:"true"`
Port int `key:"port" default:"8080"`
Timeout time.Duration `key:"timeout" default:"5s"`
Tags []string `key:"tag" env:"APP_TAGS"`
} `seg:"app"`
}
var cfg AppConfig
_, err := sysconf.LoadConfigSources(&cfg, []sysconf.ConfigSource{
sysconf.RequiredFile("/etc/app.ini"),
sysconf.OptionalFile("/etc/app.local.ini"),
}, sysconf.WithEnvPrefix("APP"))
```
The current framework is intentionally local-file focused. It does not include hot reload, remote configuration centers, secret managers, or a schema DSL.
## Scope
This package is intentionally small and cgo-free. Functionality that duplicates broad system inventory packages should stay frozen unless it improves cross-platform semantics, error observability, or compatibility for existing callers.