Implement Mihomo external-controller support in configuration and gateway
- Added optional Mihomo configuration fields in `config.compose.yaml` and `config.example.yaml` for enhanced integration with the Mihomo external-controller. - Updated the `Gateway` to handle Mihomo API requests, including proxying and error handling for Mihomo-specific endpoints. - Enhanced the documentation in `GATEWAY_RUN.md` to guide users on configuring Mihomo integration. - Introduced new utility functions in the web client for interacting with Mihomo API endpoints, improving the overall user experience. - Updated the sidebar in the Svelte components to include a link to the Mihomo section, enhancing navigation.
This commit is contained in:
@@ -49,6 +49,16 @@ type Server struct {
|
||||
BaseURL string `yaml:"base_url"`
|
||||
PathPrefix string `yaml:"path_prefix"`
|
||||
AuthorizationEnv string `yaml:"authorization_env"`
|
||||
// Mihomo external-controller (optional): REST + WebSocket at controller root.
|
||||
MihomoBaseURL string `yaml:"mihomo_base_url"`
|
||||
MihomoBaseURLEnv string `yaml:"mihomo_base_url_env"`
|
||||
MihomoAuthorizationEnv string `yaml:"mihomo_authorization_env"`
|
||||
}
|
||||
|
||||
// Mihomo holds resolved external-controller upstream for a server alias.
|
||||
type Mihomo struct {
|
||||
Base *url.URL
|
||||
Auth string // full Authorization header value (e.g. Bearer <secret>)
|
||||
}
|
||||
|
||||
// Load reads and validates configuration from path.
|
||||
@@ -79,6 +89,9 @@ func (c *Config) Validate() error {
|
||||
s.BaseURL = strings.TrimSpace(s.BaseURL)
|
||||
s.PathPrefix = strings.TrimSpace(s.PathPrefix)
|
||||
s.AuthorizationEnv = strings.TrimSpace(s.AuthorizationEnv)
|
||||
s.MihomoBaseURL = strings.TrimSpace(s.MihomoBaseURL)
|
||||
s.MihomoBaseURLEnv = strings.TrimSpace(s.MihomoBaseURLEnv)
|
||||
s.MihomoAuthorizationEnv = strings.TrimSpace(s.MihomoAuthorizationEnv)
|
||||
if s.Alias == "" {
|
||||
return fmt.Errorf("servers[%d]: alias is required", i)
|
||||
}
|
||||
@@ -106,6 +119,19 @@ func (c *Config) Validate() error {
|
||||
if !strings.HasPrefix(s.PathPrefix, "/") {
|
||||
s.PathPrefix = "/" + s.PathPrefix
|
||||
}
|
||||
hasMihomoURL := s.MihomoBaseURL != "" || s.MihomoBaseURLEnv != ""
|
||||
if hasMihomoURL && s.MihomoAuthorizationEnv == "" {
|
||||
return fmt.Errorf("servers[%d]: mihomo_authorization_env is required when mihomo url is set", i)
|
||||
}
|
||||
if !hasMihomoURL && s.MihomoAuthorizationEnv != "" {
|
||||
return fmt.Errorf("servers[%d]: mihomo_base_url or mihomo_base_url_env is required when mihomo_authorization_env is set", i)
|
||||
}
|
||||
if s.MihomoBaseURL != "" {
|
||||
mu, err := url.Parse(s.MihomoBaseURL)
|
||||
if err != nil || mu.Scheme == "" || mu.Host == "" {
|
||||
return fmt.Errorf("servers[%d]: invalid mihomo_base_url %q", i, s.MihomoBaseURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(c.Servers) == 0 {
|
||||
return fmt.Errorf("at least one server entry is required")
|
||||
@@ -160,6 +186,7 @@ type Parsed struct {
|
||||
Trusted []netip.Prefix
|
||||
ByAlias map[string]*Server
|
||||
AuthByAlias map[string]string // non-empty Authorization value per alias
|
||||
MihomoByAlias map[string]*Mihomo
|
||||
}
|
||||
|
||||
// Parse compiles CIDRs and resolves authorization from environment.
|
||||
@@ -182,6 +209,7 @@ func (c *Config) Parse() (*Parsed, error) {
|
||||
}
|
||||
by := make(map[string]*Server, len(c.Servers))
|
||||
auth := make(map[string]string)
|
||||
mihomo := make(map[string]*Mihomo)
|
||||
for i := range c.Servers {
|
||||
s := &c.Servers[i]
|
||||
by[s.Alias] = s
|
||||
@@ -192,12 +220,53 @@ func (c *Config) Parse() (*Parsed, error) {
|
||||
}
|
||||
auth[s.Alias] = v
|
||||
}
|
||||
mu, err := resolveMihomo(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mu != nil {
|
||||
mihomo[s.Alias] = mu
|
||||
}
|
||||
}
|
||||
return &Parsed{
|
||||
Config: c,
|
||||
Whitelist: wl,
|
||||
Trusted: tr,
|
||||
ByAlias: by,
|
||||
AuthByAlias: auth,
|
||||
Config: c,
|
||||
Whitelist: wl,
|
||||
Trusted: tr,
|
||||
ByAlias: by,
|
||||
AuthByAlias: auth,
|
||||
MihomoByAlias: mihomo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// resolveMihomo returns non-nil only when Mihomo is configured for this server.
|
||||
func resolveMihomo(s *Server) (*Mihomo, error) {
|
||||
hasURL := s.MihomoBaseURL != "" || s.MihomoBaseURLEnv != ""
|
||||
if !hasURL {
|
||||
return nil, nil
|
||||
}
|
||||
if s.MihomoAuthorizationEnv == "" {
|
||||
return nil, fmt.Errorf("server %q: mihomo_authorization_env is required when mihomo url is set", s.Alias)
|
||||
}
|
||||
var urlStr string
|
||||
if s.MihomoBaseURLEnv != "" {
|
||||
v := strings.TrimSpace(os.Getenv(s.MihomoBaseURLEnv))
|
||||
if v != "" {
|
||||
urlStr = v
|
||||
}
|
||||
}
|
||||
if urlStr == "" && s.MihomoBaseURL != "" {
|
||||
urlStr = s.MihomoBaseURL
|
||||
}
|
||||
if urlStr == "" {
|
||||
return nil, fmt.Errorf("server %q: mihomo controller url is empty (set mihomo_base_url or env %q)", s.Alias, s.MihomoBaseURLEnv)
|
||||
}
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return nil, fmt.Errorf("server %q: invalid mihomo controller url %q", s.Alias, urlStr)
|
||||
}
|
||||
authVal := os.Getenv(s.MihomoAuthorizationEnv)
|
||||
if strings.TrimSpace(authVal) == "" {
|
||||
return nil, fmt.Errorf("server %q: env %q is empty or unset", s.Alias, s.MihomoAuthorizationEnv)
|
||||
}
|
||||
return &Mihomo{Base: u, Auth: authVal}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user