Files
CyberStrikeAI/internal/termout/startup.go
T
苏尼克andsycpro c563567502 fix: startup banner hardcodes 127.0.0.1 regardless of server.host (#307)
The Web UI startup banner always printed 127.0.0.1, even when
server.host was bound to 0.0.0.0 or a specific interface. This made
users believe the host setting was ignored (the actual listen binding
was correct). Pass the configured host into the banner and expand
wildcard binds to the machine's real addresses so LAN URLs are shown.

Fixes #301

Co-authored-by: sycpro <sycpro@sycprodeMacBook-Pro.local>
2026-09-13 15:27:38 +08:00

115 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package termout
import (
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
)
// StartupWebUIOptions configures the startup Web UI banner.
type StartupWebUIOptions struct {
Scheme string
Host string
Port int
SelfSigned bool
HTTPRedirect bool
}
// PrintConfigCreated prints a short notice when config.yaml is bootstrapped.
func PrintConfigCreated() {
s := New(os.Stdout)
s.Println("")
s.Println(s.Green("✔ ") + s.Bold("已创建 config.yaml") + s.Dim("(来自 config.example.yaml"))
s.BlankLine()
}
// startupHosts 返回横幅应展示的访问地址。通配地址(含空 host)展开为回环地址
// 加本机非回环 IPv4;显式 host 原样展示。横幅此前硬编码 127.0.0.1,导致
// 绑定 0.0.0.0 的用户误以为 server.host 配置未生效(issue #301)。
func startupHosts(host string) []string {
host = strings.TrimSpace(host)
if host != "" && host != "0.0.0.0" && host != "::" && host != "[::]" {
return []string{host}
}
hosts := []string{"127.0.0.1"}
addrs, err := net.InterfaceAddrs()
if err != nil {
return hosts
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP.IsLoopback() || ipNet.IP.To4() == nil {
continue
}
ip := ipNet.IP.String()
dup := false
for _, h := range hosts {
if h == ip {
dup = true
break
}
}
if !dup {
hosts = append(hosts, ip)
}
}
return hosts
}
// PrintStartupWebUI prints a colored startup banner for the Web UI.
func PrintStartupWebUI(opts StartupWebUIOptions) {
printStartupWebUI(os.Stdout, opts)
}
func printStartupWebUI(out io.Writer, opts StartupWebUIOptions) {
s := New(out)
scheme := opts.Scheme
if scheme == "" {
scheme = "http"
}
port := opts.Port
if port <= 0 {
port = 8080
}
hosts := startupHosts(opts.Host)
urlFor := func(h string) string {
return scheme + "://" + net.JoinHostPort(h, strconv.Itoa(port)) + "/"
}
s.BlankLine()
s.Println(s.Bold(s.Cyan("CYBERSTRIKE AI")) + s.Dim(" / secure workspace"))
s.Println(s.Dim(strings.Repeat("─", 60)))
s.Println(s.Green("● ONLINE") + " " + s.Bold(s.White(urlFor(hosts[0]))))
for _, h := range hosts[1:] {
s.Println(s.Dim(" Network ") + s.Bold(s.White(urlFor(h))))
}
if opts.SelfSigned {
s.Println(s.Dim(" TLS ") + s.Yellow("self-signed") + s.Dim(" · accept the browser warning once"))
}
if opts.HTTPRedirect {
s.Println(s.Dim(" Redirect ") + fmt.Sprintf("http://%s/ → HTTPS", net.JoinHostPort(hosts[0], strconv.Itoa(port))))
}
s.BlankLine()
}
// PrintBootstrapAdminCredentials prints the initial admin password banner.
func PrintBootstrapAdminCredentials(password string) {
password = strings.TrimSpace(password)
if password == "" {
return
}
s := New(os.Stdout)
s.Println(s.Bold(s.Yellow("ADMIN SETUP REQUIRED")))
s.Println(s.Dim(strings.Repeat("─", 60)))
s.Println(s.Dim(" Username ") + s.Bold(s.White("admin")))
s.Println(s.Dim(" Password ") + s.Bold(s.Yellow(password)))
s.BlankLine()
s.Println(s.Yellow(" ! ") + s.White("Store this password securely. It is shown only once."))
s.Println(s.Dim(" Change it in Settings immediately after signing in."))
s.BlankLine()
}