mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-21 00:24:12 +08:00
335 lines
9.3 KiB
Go
335 lines
9.3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func (a AgentService) BindWebsite(req dto.AgentWebsiteBindReq) error {
|
|
agent, err := agentRepo.GetFirst(repo.WithByID(req.AgentID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if agent.WebsiteID != 0 {
|
|
return buserr.New("ErrAgentWebsiteBound")
|
|
}
|
|
|
|
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isBindableAgentWebsiteType(website.Type) {
|
|
return buserr.New("ErrAgentWebsiteTypeUnsupported")
|
|
}
|
|
|
|
boundAgent, err := agentRepo.GetFirst(repo.WithByWebsiteID(req.WebsiteID))
|
|
if err == nil && boundAgent.ID > 0 {
|
|
return buserr.New("ErrAgentWebsiteInUse")
|
|
}
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return err
|
|
}
|
|
|
|
agent.WebsiteID = req.WebsiteID
|
|
if err := agentRepo.Save(agent); err != nil {
|
|
return err
|
|
}
|
|
return ensureOpenclawWebsiteAllowedOrigin(agent, &website)
|
|
}
|
|
|
|
func (a AgentService) UnbindWebsite(req dto.AgentIDReq) error {
|
|
agent, err := agentRepo.GetFirst(repo.WithByID(req.AgentID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if agent.WebsiteID == 0 {
|
|
return nil
|
|
}
|
|
|
|
website, err := websiteRepo.GetFirst(repo.WithByID(agent.WebsiteID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if website.Type == constant.Deployment {
|
|
return buserr.New("ErrAgentWebsiteUnbindUnsupported")
|
|
}
|
|
|
|
agent.WebsiteID = 0
|
|
return agentRepo.Save(agent)
|
|
}
|
|
|
|
func hydrateAgentWebsiteItems(items []dto.AgentItem) error {
|
|
explicitWebsiteMap, err := loadAgentWebsiteMapByID(items)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
websiteDomainMap, err := loadAgentWebsiteDomainMapByWebsiteID(items)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fillAgentWebsiteItems(items, explicitWebsiteMap, websiteDomainMap)
|
|
return nil
|
|
}
|
|
|
|
func loadAgentWebsiteMapByID(items []dto.AgentItem) (map[uint]model.Website, error) {
|
|
websiteIDs := make([]uint, 0, len(items))
|
|
for _, item := range items {
|
|
if item.WebsiteID > 0 {
|
|
websiteIDs = append(websiteIDs, item.WebsiteID)
|
|
}
|
|
}
|
|
if len(websiteIDs) == 0 {
|
|
return map[uint]model.Website{}, nil
|
|
}
|
|
websites, err := websiteRepo.GetBy(repo.WithByIDs(uniqueUintList(websiteIDs)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
websiteMap := make(map[uint]model.Website, len(websites))
|
|
for _, website := range websites {
|
|
websiteMap[website.ID] = website
|
|
}
|
|
return websiteMap, nil
|
|
}
|
|
|
|
func loadAgentWebsiteDomainMapByWebsiteID(items []dto.AgentItem) (map[uint][]model.WebsiteDomain, error) {
|
|
websiteIDs := make([]uint, 0, len(items))
|
|
for _, item := range items {
|
|
if item.WebsiteID > 0 {
|
|
websiteIDs = append(websiteIDs, item.WebsiteID)
|
|
}
|
|
}
|
|
if len(websiteIDs) == 0 {
|
|
return map[uint][]model.WebsiteDomain{}, nil
|
|
}
|
|
websiteDomains, err := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteIds(uniqueUintList(websiteIDs)), repo.WithOrderAsc("id"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
websiteDomainMap := make(map[uint][]model.WebsiteDomain, len(websiteDomains))
|
|
for _, websiteDomain := range websiteDomains {
|
|
websiteDomainMap[websiteDomain.WebsiteID] = append(websiteDomainMap[websiteDomain.WebsiteID], websiteDomain)
|
|
}
|
|
return websiteDomainMap, nil
|
|
}
|
|
|
|
func loadAgentWebsiteResourceName(website model.Website) (string, error) {
|
|
websiteDomains, err := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID), repo.WithOrderAsc("id"))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(websiteDomains) > 0 {
|
|
return websiteDomains[0].Domain, nil
|
|
}
|
|
if website.PrimaryDomain != "" {
|
|
return website.PrimaryDomain, nil
|
|
}
|
|
return fmt.Sprintf("%d", website.ID), nil
|
|
}
|
|
|
|
func fillAgentWebsiteItems(items []dto.AgentItem, explicitWebsiteMap map[uint]model.Website, websiteDomainMap map[uint][]model.WebsiteDomain) {
|
|
for index := range items {
|
|
if items[index].WebsiteID == 0 {
|
|
continue
|
|
}
|
|
website, ok := explicitWebsiteMap[items[index].WebsiteID]
|
|
if !ok {
|
|
items[index].WebsiteID = 0
|
|
items[index].WebsitePrimaryDomain = ""
|
|
items[index].WebsiteType = ""
|
|
items[index].WebsiteProtocol = ""
|
|
continue
|
|
}
|
|
items[index].WebsiteType = website.Type
|
|
items[index].WebsiteProtocol = website.Protocol
|
|
websiteDomains := websiteDomainMap[items[index].WebsiteID]
|
|
if len(websiteDomains) == 0 {
|
|
items[index].WebsitePrimaryDomain = ""
|
|
continue
|
|
}
|
|
items[index].WebsitePrimaryDomain = websiteDomains[0].Domain
|
|
}
|
|
}
|
|
|
|
func UniqueDeploymentWebsiteMapByAppInstall(websites []model.Website) map[uint]model.Website {
|
|
websiteMap := make(map[uint]model.Website)
|
|
duplicateAppInstallIDs := make(map[uint]struct{})
|
|
for _, website := range websites {
|
|
if website.AppInstallID == 0 {
|
|
continue
|
|
}
|
|
if _, duplicated := duplicateAppInstallIDs[website.AppInstallID]; duplicated {
|
|
continue
|
|
}
|
|
if _, exists := websiteMap[website.AppInstallID]; exists {
|
|
delete(websiteMap, website.AppInstallID)
|
|
duplicateAppInstallIDs[website.AppInstallID] = struct{}{}
|
|
continue
|
|
}
|
|
websiteMap[website.AppInstallID] = website
|
|
}
|
|
return websiteMap
|
|
}
|
|
|
|
func uniqueUintList(items []uint) []uint {
|
|
itemMap := make(map[uint]struct{}, len(items))
|
|
uniq := make([]uint, 0, len(items))
|
|
for _, item := range items {
|
|
if item == 0 {
|
|
continue
|
|
}
|
|
if _, exists := itemMap[item]; exists {
|
|
continue
|
|
}
|
|
itemMap[item] = struct{}{}
|
|
uniq = append(uniq, item)
|
|
}
|
|
return uniq
|
|
}
|
|
|
|
func isBindableAgentWebsiteType(websiteType string) bool {
|
|
return websiteType == constant.Proxy || websiteType == constant.Static
|
|
}
|
|
|
|
func bindDeploymentWebsiteToAgentByAppInstall(website *model.Website) error {
|
|
if website.ID == 0 || website.Type != constant.Deployment || website.AppInstallID == 0 {
|
|
return nil
|
|
}
|
|
|
|
appInstall, err := appInstallRepo.GetFirst(repo.WithByID(website.AppInstallID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if appInstall.App.Key != constant.AppOpenclaw && appInstall.App.Key != constant.AppCopaw && appInstall.App.Key != constant.AppHermesAgent {
|
|
return nil
|
|
}
|
|
|
|
agent, err := agentRepo.GetFirst(repo.WithByAppInstallID(website.AppInstallID))
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if agent.WebsiteID != 0 {
|
|
return nil
|
|
}
|
|
|
|
agent.WebsiteID = website.ID
|
|
if err := agentRepo.Save(agent); err != nil {
|
|
return err
|
|
}
|
|
return ensureOpenclawWebsiteAllowedOrigin(agent, website)
|
|
}
|
|
|
|
func ensureOpenclawWebsiteAllowedOrigin(agent *model.Agent, website *model.Website) error {
|
|
if agent == nil || website == nil || agent.AgentType != constant.AppOpenclaw {
|
|
return nil
|
|
}
|
|
|
|
origins, err := buildWebsiteAllowedOrigins(website)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(origins) == 0 {
|
|
return nil
|
|
}
|
|
|
|
install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
allowedOrigins := extractSecurityConfig(conf).AllowedOrigins
|
|
allowedOrigins, err = normalizeAllowedOrigins(append(allowedOrigins, origins...))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
|
if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil {
|
|
return err
|
|
}
|
|
if err := syncOpenclawAllowedOriginEnv(&install, allowedOrigins); err != nil {
|
|
return err
|
|
}
|
|
return appInstallRepo.Save(context.Background(), &install)
|
|
}
|
|
|
|
func buildWebsiteAllowedOrigins(website *model.Website) ([]string, error) {
|
|
websiteDomains, err := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID), repo.WithOrderAsc("id"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(websiteDomains) == 0 {
|
|
return nil, nil
|
|
}
|
|
defaultHTTPSPort := 443
|
|
if strings.EqualFold(website.Protocol, "https") {
|
|
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
|
if err == nil && nginxInstall.HttpsPort > 0 {
|
|
defaultHTTPSPort = nginxInstall.HttpsPort
|
|
}
|
|
}
|
|
return buildWebsiteAllowedOriginsFromDomains(website, websiteDomains, defaultHTTPSPort)
|
|
}
|
|
|
|
func buildWebsiteAllowedOriginsFromDomains(website *model.Website, websiteDomains []model.WebsiteDomain, defaultHTTPSPort int) ([]string, error) {
|
|
if len(websiteDomains) == 0 {
|
|
return nil, nil
|
|
}
|
|
sort.Slice(websiteDomains, func(i, j int) bool {
|
|
return websiteDomains[i].ID < websiteDomains[j].ID
|
|
})
|
|
origins := make([]string, 0, len(websiteDomains))
|
|
for _, websiteDomain := range websiteDomains {
|
|
origin, err := buildWebsiteDomainOrigin(website.Protocol, websiteDomain, defaultHTTPSPort)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
origins = append(origins, origin)
|
|
}
|
|
return origins, nil
|
|
}
|
|
|
|
func buildWebsiteDomainOrigin(protocol string, websiteDomain model.WebsiteDomain, defaultHTTPSPort int) (string, error) {
|
|
origin := strings.ToLower(strings.TrimSpace(protocol)) + "://" + formatWebsiteDomainHost(websiteDomain.Domain)
|
|
switch strings.ToLower(strings.TrimSpace(protocol)) {
|
|
case "http":
|
|
if websiteDomain.Port > 0 && websiteDomain.Port != 80 {
|
|
origin = fmt.Sprintf("%s:%d", origin, websiteDomain.Port)
|
|
}
|
|
case "https":
|
|
port := websiteDomain.Port
|
|
if !websiteDomain.SSL {
|
|
port = defaultHTTPSPort
|
|
}
|
|
if port > 0 && port != 443 {
|
|
origin = fmt.Sprintf("%s:%d", origin, port)
|
|
}
|
|
}
|
|
return normalizeAllowedOrigin(origin)
|
|
}
|
|
|
|
func formatWebsiteDomainHost(domain string) string {
|
|
host := strings.Trim(strings.TrimSpace(domain), "[]")
|
|
if ip := net.ParseIP(host); ip != nil && strings.Contains(host, ":") {
|
|
return "[" + host + "]"
|
|
}
|
|
return host
|
|
}
|