package cli import ( "encoding/json" "fmt" "io" "path/filepath" "runtime" "strings" "time" "git.eeqj.de/sneak/secret/internal/vault" "github.com/dustin/go-humanize" "github.com/fatih/color" "github.com/spf13/afero" "github.com/spf13/cobra" ) // Version info - these are set at build time var ( //nolint:gochecknoglobals // Set at build time Version = "dev" //nolint:gochecknoglobals // Set at build time GitCommit = "unknown" //nolint:gochecknoglobals // Set at build time ) // InfoOutput represents the system information for JSON output type InfoOutput struct { Version string `json:"version"` GitCommit string `json:"gitCommit"` Author string `json:"author"` License string `json:"license"` GoVersion string `json:"goVersion"` DataDirectory string `json:"dataDirectory"` CurrentVault string `json:"currentVault"` NumVaults int `json:"numVaults"` NumSecrets int `json:"numSecrets"` TotalSize int64 `json:"totalSizeBytes"` OldestSecret time.Time `json:"oldestSecret,omitempty"` LatestSecret time.Time `json:"latestSecret,omitempty"` } // newInfoCmd returns the info command func newInfoCmd() *cobra.Command { cli := NewCLIInstance() var jsonOutput bool cmd := &cobra.Command{ Use: "info", Short: "Display system information", Long: "Display information about the secret system including version, vault statistics, and storage usage", RunE: func(cmd *cobra.Command, _ []string) error { return cli.Info(cmd, jsonOutput) }, } cmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format") return cmd } // Info displays system information func (cli *Instance) Info(cmd *cobra.Command, jsonOutput bool) error { info := InfoOutput{ Version: Version, GitCommit: GitCommit, Author: "Jeffrey Paul ", License: "WTFPL", GoVersion: runtime.Version(), DataDirectory: cli.stateDir, } // Get current vault currentVault, err := vault.GetCurrentVault(cli.fs, cli.stateDir) if err == nil { info.CurrentVault = currentVault.Name } // Count vaults vaultsDir := filepath.Join(cli.stateDir, "vaults.d") vaultEntries, err := afero.ReadDir(cli.fs, vaultsDir) if err == nil { for _, entry := range vaultEntries { if entry.IsDir() { info.NumVaults++ } } } // Gather statistics from all vaults if info.NumVaults > 0 { totalSecrets, totalSize, oldestTime, latestTime, _ := gatherVaultStats(cli.fs, vaultsDir) info.NumSecrets = totalSecrets info.TotalSize = totalSize if !oldestTime.IsZero() { info.OldestSecret = oldestTime } if !latestTime.IsZero() { info.LatestSecret = latestTime } } if jsonOutput { encoder := json.NewEncoder(cmd.OutOrStdout()) encoder.SetIndent("", " ") return encoder.Encode(info) } // Pretty print with colors and emoji return prettyPrintInfo(cmd.OutOrStdout(), info) } // prettyPrintInfo formats and prints the info in a pretty format func prettyPrintInfo(w io.Writer, info InfoOutput) error { const separatorLength = 40 bold := color.New(color.Bold) green := color.New(color.FgGreen) cyan := color.New(color.FgCyan) yellow := color.New(color.FgYellow) magenta := color.New(color.FgMagenta) _, _ = fmt.Fprintln(w) _, _ = bold.Fprintln(w, "🔐 Secret System Information") _, _ = fmt.Fprintln(w, strings.Repeat("─", separatorLength)) _, _ = fmt.Fprintf(w, "📦 Version: %s\n", green.Sprint(info.Version)) _, _ = fmt.Fprintf(w, "🔧 Git Commit: %s\n", cyan.Sprint(info.GitCommit)) _, _ = fmt.Fprintf(w, "👤 Author: %s\n", cyan.Sprint(info.Author)) _, _ = fmt.Fprintf(w, "📜 License: %s\n", cyan.Sprint(info.License)) _, _ = fmt.Fprintf(w, "🐹 Go Version: %s\n", cyan.Sprint(info.GoVersion)) _, _ = fmt.Fprintf(w, "📁 Data Directory: %s\n", yellow.Sprint(info.DataDirectory)) if info.CurrentVault != "" { _, _ = fmt.Fprintf(w, "🗄️ Current Vault: %s\n", magenta.Sprint(info.CurrentVault)) } else { _, _ = fmt.Fprintf(w, "🗄️ Current Vault: %s\n", color.RedString("(none)")) } _, _ = fmt.Fprintln(w, strings.Repeat("─", separatorLength)) _, _ = fmt.Fprintf(w, "🗂️ Vaults: %s\n", bold.Sprint(info.NumVaults)) _, _ = fmt.Fprintf(w, "🔑 Secrets: %s\n", bold.Sprint(info.NumSecrets)) if info.TotalSize >= 0 { //nolint:gosec // TotalSize is always >= 0 _, _ = fmt.Fprintf(w, "💾 Total Size: %s\n", bold.Sprint(humanize.Bytes(uint64(info.TotalSize)))) } else { _, _ = fmt.Fprintf(w, "💾 Total Size: %s\n", bold.Sprint("0 B")) } if !info.OldestSecret.IsZero() { _, _ = fmt.Fprintf(w, "🕰️ Oldest Secret: %s\n", info.OldestSecret.Format("2006-01-02 15:04:05")) } if !info.LatestSecret.IsZero() { _, _ = fmt.Fprintf(w, "✨ Latest Secret: %s\n", info.LatestSecret.Format("2006-01-02 15:04:05")) } _, _ = fmt.Fprintln(w) return nil }