package edgecmd import ( "fmt" "io" "net" "os" "sort" "strings" "github.com/spf13/cobra" "iop/packages/go/config" ) // envCmd prints the resolved environment for the bundled edge binary so the // user can verify config discovery, advertise host, log path, transport // address, artifact/bootstrap URL, and OpenAI-compatible URL without starting // edge. It does not bind any port or connect to nodes. func envCmd() *cobra.Command { return &cobra.Command{ Use: "env", Short: "Print resolved edge environment without starting edge", RunE: func(cmd *cobra.Command, _ []string) error { cfg, path, err := LoadEdgeConfig(cmd) if err != nil { return err } logPath, err := ResolveEdgeLogPath(cfg.Logging.Path) if err != nil { return err } cfg.Bootstrap.ArtifactDir = ResolveBootstrapArtifactDir(cfg.Bootstrap.ArtifactDir) renderEnv(cmd.OutOrStdout(), envReport{ ConfigPath: path, BinaryDir: binaryDir(), LogPath: logPath, Advertise: resolveAdvertiseHost(cfg.Server.AdvertiseHost), Server: cfg.Server, Bootstrap: cfg.Bootstrap, OpenAI: cfg.OpenAI, A2A: cfg.A2A, Nodes: cfg.Nodes, }) return nil }, } } type advertise struct { Host string Source string } func resolveAdvertiseHost(explicit string) advertise { if explicit != "" { return advertise{Host: explicit, Source: "explicit"} } if ip := nonLoopbackIPv4(); ip != "" { return advertise{Host: ip, Source: "auto"} } if hn, err := os.Hostname(); err == nil && hn != "" { return advertise{Host: hn, Source: "auto"} } return advertise{Host: "127.0.0.1", Source: "auto"} } func nonLoopbackIPv4() string { addrs, err := net.InterfaceAddrs() if err != nil { return "" } for _, a := range addrs { ipnet, ok := a.(*net.IPNet) if !ok || ipnet.IP.IsLoopback() { continue } ip4 := ipnet.IP.To4() if ip4 == nil { continue } return ip4.String() } return "" } // addressFor combines an advertise host with the port from a listen string // (e.g. "0.0.0.0:9090" -> ":9090"). If the listen string is invalid it // is returned unchanged. func addressFor(host, listen string) string { _, port, err := net.SplitHostPort(listen) if err != nil || port == "" { return listen } return net.JoinHostPort(host, port) } func defaultArtifactBaseURL(host string, listen ...string) string { bootstrapListen := "0.0.0.0:18080" if len(listen) > 0 && listen[0] != "" { bootstrapListen = listen[0] } return "http://" + addressFor(host, bootstrapListen) } func bootstrapScriptURL(baseURL, target string) string { return strings.TrimSuffix(baseURL, "/") + "/bootstrap/node-" + target + ".sh" } func bootstrapPowerShellScriptURL(baseURL, target string) string { return strings.TrimSuffix(baseURL, "/") + "/bootstrap/node-" + target + ".ps1" } func universalBootstrapScriptURL(baseURL string) string { return strings.TrimSuffix(baseURL, "/") + "/bootstrap/node.sh" } type envReport struct { ConfigPath string BinaryDir string LogPath string Advertise advertise Server config.EdgeServerConf Bootstrap config.EdgeBootstrapConf OpenAI config.EdgeOpenAIConf A2A config.EdgeA2AConf Nodes []config.NodeDefinition } func renderEnv(w io.Writer, r envReport) { artifactURL := strings.TrimSuffix(r.Bootstrap.ArtifactBaseURL, "/") artifactSource := "configured" if artifactURL == "" { artifactURL = defaultArtifactBaseURL(r.Advertise.Host, r.Bootstrap.Listen) artifactSource = "derived" } fmt.Fprintf(w, "config: %s\n", r.ConfigPath) fmt.Fprintf(w, "binary_dir: %s\n", r.BinaryDir) fmt.Fprintf(w, "log_path: %s\n", r.LogPath) fmt.Fprintf(w, "advertise_host: %s (%s)\n", r.Advertise.Host, r.Advertise.Source) fmt.Fprintf(w, "node_transport: %s\n", addressFor(r.Advertise.Host, r.Server.Listen)) fmt.Fprintf(w, "artifact_dir: %s\n", r.Bootstrap.ArtifactDir) fmt.Fprintf(w, "artifact_listen: %s\n", r.Bootstrap.Listen) fmt.Fprintf(w, "artifact_base_url: %s (%s)\n", artifactURL, artifactSource) fmt.Fprintf(w, "node_bootstrap: %s\n", universalBootstrapScriptURL(artifactURL)) if r.OpenAI.Enabled { fmt.Fprintf(w, "openai_url: http://%s/v1\n", addressFor(r.Advertise.Host, r.OpenAI.Listen)) } else { fmt.Fprintln(w, "openai_url: (disabled)") } if r.A2A.Enabled { path := r.A2A.Path if path == "" { path = "/" } fmt.Fprintf(w, "a2a_url: http://%s%s\n", addressFor(r.Advertise.Host, r.A2A.Listen), path) } else { fmt.Fprintln(w, "a2a_url: (disabled)") } fmt.Fprintf(w, "configured_nodes: %s\n", summarizeNodes(r.Nodes)) fmt.Fprintln(w, "connected_nodes: offline (env does not start edge)") } func summarizeNodes(nodes []config.NodeDefinition) string { if len(nodes) == 0 { return "0 (none)" } labels := make([]string, 0, len(nodes)) for _, n := range nodes { label := n.ID if label == "" { label = n.Alias } if label == "" { label = "(unnamed)" } labels = append(labels, label) } sort.Strings(labels) return fmt.Sprintf("%d (%s)", len(nodes), strings.Join(labels, ", ")) }