sysinfo/internal/sysinfo/app.go
2025-05-01 03:25:38 -07:00

120 lines
2.4 KiB
Go

package sysinfo
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type App struct {
force bool
jsonOut bool
verbose bool
cmd *cobra.Command
}
func NewApp() (*App, error) {
a := &App{}
root := &cobra.Command{
Use: "sysinfo",
Short: "capture system snapshot",
Version: Version,
SilenceUsage: true,
SilenceErrors: true,
RunE: a.run,
}
root.Flags().BoolVarP(&a.force, "force", "f", false,
"overwrite /etc/sysinfo")
root.Flags().BoolVar(&a.jsonOut, "json", false,
"emit JSON snapshot to stdout")
root.Flags().BoolVarP(&a.verbose, "verbose", "v", false,
"verbose progress")
_ = viper.BindPFlags(root.Flags())
root.AddCommand(&cobra.Command{
Use: "schema",
Short: "print JSON schema",
Run: func(*cobra.Command, []string) {
fmt.Println(JSONSchema)
},
})
a.cmd = root
return a, nil
}
func (a *App) Execute() error { return a.cmd.Execute() }
func (a *App) logf(f string, v ...any) {
if a.verbose {
_, _ = fmt.Fprintf(os.Stderr, f+"\n", v...)
}
}
func (a *App) run(*cobra.Command, []string) error {
if err := ensureUbuntu(); err != nil {
return err
}
pm := newPackageManager(a.logf)
ctx := &Context{
Now: time.Now().UTC(),
Logf: a.logf,
Run: runCmd,
SafeRun: safeRun,
SafeRead: safeRead,
}
snap := &Snapshot{
Timestamp: ctx.Now.Format(time.RFC3339),
Sections: map[string]json.RawMessage{},
}
collectors := []Collector{
SystemCollector{}, BlockCollector{},
NetworkCollector{}, SensorsCollector{},
PackagesCollector{}, ZFSCollector{},
}
for _, col := range collectors {
if err := col.EnsurePrerequisites(pm, ctx); err != nil {
ctx.Logf("%s: prereq error: %v", col.SectionKey(), err)
continue
}
if !col.IsSupported(pm, ctx) {
ctx.Logf("%s: not supported on this host", col.SectionKey())
continue
}
raw, err := col.CollectData(ctx)
if err != nil {
ctx.Logf("%s: %v", col.SectionKey(), err)
continue
}
snap.Sections[col.SectionKey()] = raw
}
if a.jsonOut {
return emitJSON(os.Stdout, snap)
}
if _, err := os.Stat(rootDir); err == nil && !a.force {
return fmt.Errorf("%s exists; use --force", rootDir)
}
_ = os.RemoveAll(rootDir)
if err := os.MkdirAll(rootDir, 0755); err != nil {
return err
}
f, err := os.Create(filepath.Join(rootDir, snapshotJSON))
if err != nil {
return err
}
defer f.Close()
return emitJSON(f, snap)
}