docs: update docs about insecure skip verify

This commit is contained in:
Jacky 2024-07-30 15:17:43 +08:00
parent f1c0f8ddca
commit 6c7b644f60
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
9 changed files with 467 additions and 443 deletions

View file

@ -1,18 +1,18 @@
package openai package openai
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/0xJacky/Nginx-UI/api" "github.com/0xJacky/Nginx-UI/api"
"github.com/0xJacky/Nginx-UI/internal/chatbot" "github.com/0xJacky/Nginx-UI/internal/chatbot"
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
) )
const ChatGPTInitPrompt = `You are a assistant who can help users write and optimise the configurations of Nginx, const ChatGPTInitPrompt = `You are a assistant who can help users write and optimise the configurations of Nginx,
@ -22,111 +22,111 @@ Later the language environment depends on the user message.
The first reply should involve the key information of the file and ask user what can you help them.` The first reply should involve the key information of the file and ask user what can you help them.`
func MakeChatCompletionRequest(c *gin.Context) { func MakeChatCompletionRequest(c *gin.Context) {
var json struct { var json struct {
Filepath string `json:"filepath"` Filepath string `json:"filepath"`
Messages []openai.ChatCompletionMessage `json:"messages"` Messages []openai.ChatCompletionMessage `json:"messages"`
} }
if !api.BindAndValid(c, &json) { if !api.BindAndValid(c, &json) {
return return
} }
messages := []openai.ChatCompletionMessage{ messages := []openai.ChatCompletionMessage{
{ {
Role: openai.ChatMessageRoleSystem, Role: openai.ChatMessageRoleSystem,
Content: ChatGPTInitPrompt, Content: ChatGPTInitPrompt,
}, },
} }
messages = append(messages, json.Messages...) messages = append(messages, json.Messages...)
if json.Filepath != "" { if json.Filepath != "" {
messages = chatbot.ChatCompletionWithContext(json.Filepath, messages) messages = chatbot.ChatCompletionWithContext(json.Filepath, messages)
} }
// SSE server // SSE server
c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8") c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
c.Writer.Header().Set("Cache-Control", "no-cache") c.Writer.Header().Set("Cache-Control", "no-cache")
c.Writer.Header().Set("Connection", "keep-alive") c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
config := openai.DefaultConfig(settings.OpenAISettings.Token) config := openai.DefaultConfig(settings.OpenAISettings.Token)
if settings.OpenAISettings.Proxy != "" { if settings.OpenAISettings.Proxy != "" {
proxyUrl, err := url.Parse(settings.OpenAISettings.Proxy) proxyUrl, err := url.Parse(settings.OpenAISettings.Proxy)
if err != nil { if err != nil {
c.Stream(func(w io.Writer) bool { c.Stream(func(w io.Writer) bool {
c.SSEvent("message", gin.H{ c.SSEvent("message", gin.H{
"type": "error", "type": "error",
"content": err.Error(), "content": err.Error(),
}) })
return false return false
}) })
return return
} }
transport := &http.Transport{ transport := &http.Transport{
Proxy: http.ProxyURL(proxyUrl), Proxy: http.ProxyURL(proxyUrl),
TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify}, TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify},
} }
config.HTTPClient = &http.Client{ config.HTTPClient = &http.Client{
Transport: transport, Transport: transport,
} }
} }
if settings.OpenAISettings.BaseUrl != "" { if settings.OpenAISettings.BaseUrl != "" {
config.BaseURL = settings.OpenAISettings.BaseUrl config.BaseURL = settings.OpenAISettings.BaseUrl
} }
openaiClient := openai.NewClientWithConfig(config) openaiClient := openai.NewClientWithConfig(config)
ctx := context.Background() ctx := context.Background()
req := openai.ChatCompletionRequest{ req := openai.ChatCompletionRequest{
Model: settings.OpenAISettings.Model, Model: settings.OpenAISettings.Model,
Messages: messages, Messages: messages,
Stream: true, Stream: true,
} }
stream, err := openaiClient.CreateChatCompletionStream(ctx, req) stream, err := openaiClient.CreateChatCompletionStream(ctx, req)
if err != nil { if err != nil {
fmt.Printf("CompletionStream error: %v\n", err) fmt.Printf("CompletionStream error: %v\n", err)
c.Stream(func(w io.Writer) bool { c.Stream(func(w io.Writer) bool {
c.SSEvent("message", gin.H{ c.SSEvent("message", gin.H{
"type": "error", "type": "error",
"content": err.Error(), "content": err.Error(),
}) })
return false return false
}) })
return return
} }
defer stream.Close() defer stream.Close()
msgChan := make(chan string) msgChan := make(chan string)
go func() { go func() {
defer close(msgChan) defer close(msgChan)
for { for {
response, err := stream.Recv() response, err := stream.Recv()
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
fmt.Println() fmt.Println()
return return
} }
if err != nil { if err != nil {
fmt.Printf("Stream error: %v\n", err) fmt.Printf("Stream error: %v\n", err)
return return
} }
message := fmt.Sprintf("%s", response.Choices[0].Delta.Content) message := fmt.Sprintf("%s", response.Choices[0].Delta.Content)
msgChan <- message msgChan <- message
} }
}() }()
c.Stream(func(w io.Writer) bool { c.Stream(func(w io.Writer) bool {
if m, ok := <-msgChan; ok { if m, ok := <-msgChan; ok {
c.SSEvent("message", gin.H{ c.SSEvent("message", gin.H{
"type": "message", "type": "message",
"content": m, "content": m,
}) })
return true return true
} }
return false return false
}) })
} }

View file

@ -145,3 +145,10 @@ Nginx UI will not create a system initial acme user, this means you can't apply
- Type: `string` - Type: `string`
Use this option to customize the name of local server to be displayed in the environment indicator. Use this option to customize the name of local server to be displayed in the environment indicator.
## InsecureSkipVerify
- Version`>= v2.0.0-beta.30`
- Type: `bool`
This option is used to skip the verification of the certificate of servers when Nginx UI sends requests to them.

View file

@ -3,24 +3,25 @@ Applicable for version v2.0.0-beta.23 and above.
## Server ## Server
| Configuration Setting | Environment Variable | | Configuration Setting | Environment Variable |
| ----------------------------- | ------------------------------------- | |-----------------------|---------------------------------------|
| HttpPort | NGINX_UI_SERVER_HTTP_PORT | | HttpPort | NGINX_UI_SERVER_HTTP_PORT |
| RunMode | NGINX_UI_SERVER_RUN_MODE | | RunMode | NGINX_UI_SERVER_RUN_MODE |
| JwtSecret | NGINX_UI_SERVER_JWT_SECRET | | JwtSecret | NGINX_UI_SERVER_JWT_SECRET |
| HTTPChallengePort | NGINX_UI_SERVER_HTTP_CHALLENGE_PORT | | HTTPChallengePort | NGINX_UI_SERVER_HTTP_CHALLENGE_PORT |
| StartCmd | NGINX_UI_SERVER_START_CMD | | StartCmd | NGINX_UI_SERVER_START_CMD |
| Database | NGINX_UI_SERVER_DATABASE | | Database | NGINX_UI_SERVER_DATABASE |
| CADir | NGINX_UI_SERVER_CA_DIR | | CADir | NGINX_UI_SERVER_CA_DIR |
| GithubProxy | NGINX_UI_SERVER_GITHUB_PROXY | | GithubProxy | NGINX_UI_SERVER_GITHUB_PROXY |
| NodeSecret | NGINX_UI_SERVER_NODE_SECRET | | NodeSecret | NGINX_UI_SERVER_NODE_SECRET |
| Demo | NGINX_UI_SERVER_DEMO | | Demo | NGINX_UI_SERVER_DEMO |
| PageSize | NGINX_UI_SERVER_PAGE_SIZE | | PageSize | NGINX_UI_SERVER_PAGE_SIZE |
| HttpHost | NGINX_UI_SERVER_HTTP_HOST | | HttpHost | NGINX_UI_SERVER_HTTP_HOST |
| CertRenewalInterval | NGINX_UI_SERVER_CERT_RENEWAL_INTERVAL | | CertRenewalInterval | NGINX_UI_SERVER_CERT_RENEWAL_INTERVAL |
| RecursiveNameservers | NGINX_UI_SERVER_RECURSIVE_NAMESERVERS | | RecursiveNameservers | NGINX_UI_SERVER_RECURSIVE_NAMESERVERS |
| SkipInstallation | NGINX_UI_SERVER_SKIP_INSTALLATION | | SkipInstallation | NGINX_UI_SERVER_SKIP_INSTALLATION |
| Name | NGINX_UI_SERVER_NAME | | Name | NGINX_UI_SERVER_NAME |
| InsecureSkipVerify | NGINX_UI_SERVER_INSECURE_SKIP_VERIFY |
## Nginx ## Nginx

View file

@ -132,3 +132,10 @@ Nginx UI 将不会创建系统初始的 acme 用户,这意味着您无法在
- 类型:`string` - 类型:`string`
使用此选项自定义本地服务器的名称,以在环境指示器中显示。 使用此选项自定义本地服务器的名称,以在环境指示器中显示。
## InsecureSkipVerify
- 版本:`>= v2.0.0-beta.30`
- 类型: `bool`
此选项用于配置 Nginx UI 服务器在与其他服务器建立 TLS 连接时是否跳过证书验证。

View file

@ -3,24 +3,25 @@
## Server ## Server
| Configuration Setting | Environment Variable | | Configuration Setting | Environment Variable |
| ----------------------------- | ------------------------------------- | |-------------------------| ------------------------------------- |
| HttpPort | NGINX_UI_SERVER_HTTP_PORT | | HttpPort | NGINX_UI_SERVER_HTTP_PORT |
| RunMode | NGINX_UI_SERVER_RUN_MODE | | RunMode | NGINX_UI_SERVER_RUN_MODE |
| JwtSecret | NGINX_UI_SERVER_JWT_SECRET | | JwtSecret | NGINX_UI_SERVER_JWT_SECRET |
| HTTPChallengePort | NGINX_UI_SERVER_HTTP_CHALLENGE_PORT | | HTTPChallengePort | NGINX_UI_SERVER_HTTP_CHALLENGE_PORT |
| StartCmd | NGINX_UI_SERVER_START_CMD | | StartCmd | NGINX_UI_SERVER_START_CMD |
| Database | NGINX_UI_SERVER_DATABASE | | Database | NGINX_UI_SERVER_DATABASE |
| CADir | NGINX_UI_SERVER_CA_DIR | | CADir | NGINX_UI_SERVER_CA_DIR |
| GithubProxy | NGINX_UI_SERVER_GITHUB_PROXY | | GithubProxy | NGINX_UI_SERVER_GITHUB_PROXY |
| NodeSecret | NGINX_UI_SERVER_NODE_SECRET | | NodeSecret | NGINX_UI_SERVER_NODE_SECRET |
| Demo | NGINX_UI_SERVER_DEMO | | Demo | NGINX_UI_SERVER_DEMO |
| PageSize | NGINX_UI_SERVER_PAGE_SIZE | | PageSize | NGINX_UI_SERVER_PAGE_SIZE |
| HttpHost | NGINX_UI_SERVER_HTTP_HOST | | HttpHost | NGINX_UI_SERVER_HTTP_HOST |
| CertRenewalInterval | NGINX_UI_SERVER_CERT_RENEWAL_INTERVAL | | CertRenewalInterval | NGINX_UI_SERVER_CERT_RENEWAL_INTERVAL |
| RecursiveNameservers | NGINX_UI_SERVER_RECURSIVE_NAMESERVERS | | RecursiveNameservers | NGINX_UI_SERVER_RECURSIVE_NAMESERVERS |
| SkipInstallation | NGINX_UI_SERVER_SKIP_INSTALLATION | | SkipInstallation | NGINX_UI_SERVER_SKIP_INSTALLATION |
| Name | NGINX_UI_SERVER_NAME | | Name | NGINX_UI_SERVER_NAME |
| InsecureSkipVerify | NGINX_UI_SERVER_INSECURE_SKIP_VERIFY |
## Nginx ## Nginx

View file

@ -133,3 +133,10 @@ Nginx UI 將不會創建系統初始的 acme 使用者,這意味著您無法
- 類型:`string` - 類型:`string`
使用此選項自定義本地伺服器的名稱,以在環境指示器中顯示。 使用此選項自定義本地伺服器的名稱,以在環境指示器中顯示。
## InsecureSkipVerify
- 版本:`>= v2.0.0-beta.30`
- 類型: `bool`
此選項用於配置 Nginx UI 伺服器在與其他伺服器建立 TLS 連接時是否跳過證書驗證。

View file

@ -3,24 +3,25 @@
## Server ## Server
| Configuration Setting | Environment Variable | | Configuration Setting | Environment Variable |
| ----------------------------- | ------------------------------------- | |------------------------| ------------------------------------- |
| HttpPort | NGINX_UI_SERVER_HTTP_PORT | | HttpPort | NGINX_UI_SERVER_HTTP_PORT |
| RunMode | NGINX_UI_SERVER_RUN_MODE | | RunMode | NGINX_UI_SERVER_RUN_MODE |
| JwtSecret | NGINX_UI_SERVER_JWT_SECRET | | JwtSecret | NGINX_UI_SERVER_JWT_SECRET |
| HTTPChallengePort | NGINX_UI_SERVER_HTTP_CHALLENGE_PORT | | HTTPChallengePort | NGINX_UI_SERVER_HTTP_CHALLENGE_PORT |
| StartCmd | NGINX_UI_SERVER_START_CMD | | StartCmd | NGINX_UI_SERVER_START_CMD |
| Database | NGINX_UI_SERVER_DATABASE | | Database | NGINX_UI_SERVER_DATABASE |
| CADir | NGINX_UI_SERVER_CA_DIR | | CADir | NGINX_UI_SERVER_CA_DIR |
| GithubProxy | NGINX_UI_SERVER_GITHUB_PROXY | | GithubProxy | NGINX_UI_SERVER_GITHUB_PROXY |
| NodeSecret | NGINX_UI_SERVER_NODE_SECRET | | NodeSecret | NGINX_UI_SERVER_NODE_SECRET |
| Demo | NGINX_UI_SERVER_DEMO | | Demo | NGINX_UI_SERVER_DEMO |
| PageSize | NGINX_UI_SERVER_PAGE_SIZE | | PageSize | NGINX_UI_SERVER_PAGE_SIZE |
| HttpHost | NGINX_UI_SERVER_HTTP_HOST | | HttpHost | NGINX_UI_SERVER_HTTP_HOST |
| CertRenewalInterval | NGINX_UI_SERVER_CERT_RENEWAL_INTERVAL | | CertRenewalInterval | NGINX_UI_SERVER_CERT_RENEWAL_INTERVAL |
| RecursiveNameservers | NGINX_UI_SERVER_RECURSIVE_NAMESERVERS | | RecursiveNameservers | NGINX_UI_SERVER_RECURSIVE_NAMESERVERS |
| SkipInstallation | NGINX_UI_SERVER_SKIP_INSTALLATION | | SkipInstallation | NGINX_UI_SERVER_SKIP_INSTALLATION |
| Name | NGINX_UI_SERVER_NAME | | Name | NGINX_UI_SERVER_NAME |
| InsecureSkipVerify | NGINX_UI_SERVER_INSECURE_SKIP_VERIFY |
## Nginx ## Nginx

View file

@ -1,44 +1,44 @@
package analytic package analytic
import ( import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"github.com/0xJacky/Nginx-UI/internal/logger" "github.com/0xJacky/Nginx-UI/internal/logger"
"github.com/0xJacky/Nginx-UI/internal/upgrader" "github.com/0xJacky/Nginx-UI/internal/upgrader"
"github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/shirou/gopsutil/v3/load" "github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/net" "github.com/shirou/gopsutil/v3/net"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"sync" "sync"
"time" "time"
) )
type NodeInfo struct { type NodeInfo struct {
NodeRuntimeInfo upgrader.RuntimeInfo `json:"node_runtime_info"` NodeRuntimeInfo upgrader.RuntimeInfo `json:"node_runtime_info"`
Version string `json:"version"` Version string `json:"version"`
CPUNum int `json:"cpu_num"` CPUNum int `json:"cpu_num"`
MemoryTotal string `json:"memory_total"` MemoryTotal string `json:"memory_total"`
DiskTotal string `json:"disk_total"` DiskTotal string `json:"disk_total"`
} }
type NodeStat struct { type NodeStat struct {
AvgLoad *load.AvgStat `json:"avg_load"` AvgLoad *load.AvgStat `json:"avg_load"`
CPUPercent float64 `json:"cpu_percent"` CPUPercent float64 `json:"cpu_percent"`
MemoryPercent float64 `json:"memory_percent"` MemoryPercent float64 `json:"memory_percent"`
DiskPercent float64 `json:"disk_percent"` DiskPercent float64 `json:"disk_percent"`
Network net.IOCountersStat `json:"network"` Network net.IOCountersStat `json:"network"`
Status bool `json:"status"` Status bool `json:"status"`
ResponseAt time.Time `json:"response_at"` ResponseAt time.Time `json:"response_at"`
} }
type Node struct { type Node struct {
EnvironmentID int `json:"environment_id,omitempty"` EnvironmentID int `json:"environment_id,omitempty"`
*model.Environment *model.Environment
NodeStat NodeStat
NodeInfo NodeInfo
} }
var mutex sync.Mutex var mutex sync.Mutex
@ -48,74 +48,74 @@ type TNodeMap map[int]*Node
var NodeMap TNodeMap var NodeMap TNodeMap
func init() { func init() {
NodeMap = make(TNodeMap) NodeMap = make(TNodeMap)
} }
func GetNode(env *model.Environment) (n *Node) { func GetNode(env *model.Environment) (n *Node) {
if env == nil { if env == nil {
// this should never happen // this should never happen
logger.Error("env is nil") logger.Error("env is nil")
return return
} }
if !env.Enabled { if !env.Enabled {
return &Node{ return &Node{
Environment: env, Environment: env,
} }
} }
n, ok := NodeMap[env.ID] n, ok := NodeMap[env.ID]
if !ok { if !ok {
n = &Node{} n = &Node{}
} }
n.Environment = env n.Environment = env
return n return n
} }
func InitNode(env *model.Environment) (n *Node) { func InitNode(env *model.Environment) (n *Node) {
n = &Node{ n = &Node{
Environment: env, Environment: env,
} }
u, err := url.JoinPath(env.URL, "/api/node") u, err := url.JoinPath(env.URL, "/api/node")
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return return
} }
client := http.Client{ client := http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify}, TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify},
}, },
} }
req, err := http.NewRequest("GET", u, nil) req, err := http.NewRequest("GET", u, nil)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return return
} }
req.Header.Set("X-Node-Secret", env.Token) req.Header.Set("X-Node-Secret", env.Token)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
bytes, _ := io.ReadAll(resp.Body) bytes, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
logger.Error(string(bytes)) logger.Error(string(bytes))
return return
} }
err = json.Unmarshal(bytes, &n.NodeInfo) err = json.Unmarshal(bytes, &n.NodeInfo)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return return
} }
return return
} }

View file

@ -1,258 +1,258 @@
package config package config
import ( import (
"bytes" "bytes"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/0xJacky/Nginx-UI/internal/helper" "github.com/0xJacky/Nginx-UI/internal/helper"
"github.com/0xJacky/Nginx-UI/internal/logger" "github.com/0xJacky/Nginx-UI/internal/logger"
"github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/0xJacky/Nginx-UI/internal/nginx"
"github.com/0xJacky/Nginx-UI/internal/notification" "github.com/0xJacky/Nginx-UI/internal/notification"
"github.com/0xJacky/Nginx-UI/model" "github.com/0xJacky/Nginx-UI/model"
"github.com/0xJacky/Nginx-UI/query" "github.com/0xJacky/Nginx-UI/query"
"github.com/0xJacky/Nginx-UI/settings" "github.com/0xJacky/Nginx-UI/settings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io" "io"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
) )
type SyncConfigPayload struct { type SyncConfigPayload struct {
Name string `json:"name"` Name string `json:"name"`
Filepath string `json:"filepath"` Filepath string `json:"filepath"`
NewFilepath string `json:"new_filepath"` NewFilepath string `json:"new_filepath"`
Content string `json:"content"` Content string `json:"content"`
Overwrite bool `json:"overwrite"` Overwrite bool `json:"overwrite"`
} }
func SyncToRemoteServer(c *model.Config, newFilepath string) (err error) { func SyncToRemoteServer(c *model.Config, newFilepath string) (err error) {
if c.Filepath == "" || len(c.SyncNodeIds) == 0 { if c.Filepath == "" || len(c.SyncNodeIds) == 0 {
return return
} }
nginxConfPath := nginx.GetConfPath() nginxConfPath := nginx.GetConfPath()
if !helper.IsUnderDirectory(c.Filepath, nginxConfPath) { if !helper.IsUnderDirectory(c.Filepath, nginxConfPath) {
return fmt.Errorf("config: %s is not under the nginx conf path: %s", return fmt.Errorf("config: %s is not under the nginx conf path: %s",
c.Filepath, nginxConfPath) c.Filepath, nginxConfPath)
} }
if newFilepath != "" && !helper.IsUnderDirectory(newFilepath, nginxConfPath) { if newFilepath != "" && !helper.IsUnderDirectory(newFilepath, nginxConfPath) {
return fmt.Errorf("config: %s is not under the nginx conf path: %s", return fmt.Errorf("config: %s is not under the nginx conf path: %s",
c.Filepath, nginxConfPath) c.Filepath, nginxConfPath)
} }
currentPath := c.Filepath currentPath := c.Filepath
if newFilepath != "" { if newFilepath != "" {
currentPath = newFilepath currentPath = newFilepath
} }
configBytes, err := os.ReadFile(currentPath) configBytes, err := os.ReadFile(currentPath)
if err != nil { if err != nil {
return return
} }
payload := &SyncConfigPayload{ payload := &SyncConfigPayload{
Name: c.Name, Name: c.Name,
Filepath: c.Filepath, Filepath: c.Filepath,
NewFilepath: newFilepath, NewFilepath: newFilepath,
Content: string(configBytes), Content: string(configBytes),
Overwrite: c.SyncOverwrite, Overwrite: c.SyncOverwrite,
} }
payloadBytes, err := json.Marshal(payload) payloadBytes, err := json.Marshal(payload)
if err != nil { if err != nil {
return return
} }
q := query.Environment q := query.Environment
envs, _ := q.Where(q.ID.In(c.SyncNodeIds...)).Find() envs, _ := q.Where(q.ID.In(c.SyncNodeIds...)).Find()
for _, env := range envs { for _, env := range envs {
go func() { go func() {
err := payload.deploy(env, c, payloadBytes) err := payload.deploy(env, c, payloadBytes)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
} }
}() }()
} }
return return
} }
func SyncRenameOnRemoteServer(origPath, newPath string, syncNodeIds []int) (err error) { func SyncRenameOnRemoteServer(origPath, newPath string, syncNodeIds []int) (err error) {
if origPath == "" || newPath == "" || len(syncNodeIds) == 0 { if origPath == "" || newPath == "" || len(syncNodeIds) == 0 {
return return
} }
nginxConfPath := nginx.GetConfPath() nginxConfPath := nginx.GetConfPath()
if !helper.IsUnderDirectory(origPath, nginxConfPath) { if !helper.IsUnderDirectory(origPath, nginxConfPath) {
return fmt.Errorf("config: %s is not under the nginx conf path: %s", return fmt.Errorf("config: %s is not under the nginx conf path: %s",
origPath, nginxConfPath) origPath, nginxConfPath)
} }
if !helper.IsUnderDirectory(newPath, nginxConfPath) { if !helper.IsUnderDirectory(newPath, nginxConfPath) {
return fmt.Errorf("config: %s is not under the nginx conf path: %s", return fmt.Errorf("config: %s is not under the nginx conf path: %s",
newPath, nginxConfPath) newPath, nginxConfPath)
} }
payload := &RenameConfigPayload{ payload := &RenameConfigPayload{
Filepath: origPath, Filepath: origPath,
NewFilepath: newPath, NewFilepath: newPath,
} }
q := query.Environment q := query.Environment
envs, _ := q.Where(q.ID.In(syncNodeIds...)).Find() envs, _ := q.Where(q.ID.In(syncNodeIds...)).Find()
for _, env := range envs { for _, env := range envs {
go func() { go func() {
err := payload.rename(env) err := payload.rename(env)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
} }
}() }()
} }
return return
} }
type SyncNotificationPayload struct { type SyncNotificationPayload struct {
StatusCode int `json:"status_code"` StatusCode int `json:"status_code"`
ConfigName string `json:"config_name"` ConfigName string `json:"config_name"`
EnvName string `json:"env_name"` EnvName string `json:"env_name"`
RespBody string `json:"resp_body"` RespBody string `json:"resp_body"`
} }
func (p *SyncConfigPayload) deploy(env *model.Environment, c *model.Config, payloadBytes []byte) (err error) { func (p *SyncConfigPayload) deploy(env *model.Environment, c *model.Config, payloadBytes []byte) (err error) {
client := http.Client{ client := http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify}, TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify},
}, },
} }
url, err := env.GetUrl("/api/config") url, err := env.GetUrl("/api/config")
if err != nil { if err != nil {
return return
} }
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes)) req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes))
if err != nil { if err != nil {
return return
} }
req.Header.Set("X-Node-Secret", env.Token) req.Header.Set("X-Node-Secret", env.Token)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return return
} }
notificationPayload := &SyncNotificationPayload{ notificationPayload := &SyncNotificationPayload{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
ConfigName: c.Name, ConfigName: c.Name,
EnvName: env.Name, EnvName: env.Name,
RespBody: string(respBody), RespBody: string(respBody),
} }
notificationPayloadBytes, err := json.Marshal(notificationPayload) notificationPayloadBytes, err := json.Marshal(notificationPayload)
if err != nil { if err != nil {
return return
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
notification.Error("Sync Config Error", string(notificationPayloadBytes)) notification.Error("Sync Config Error", string(notificationPayloadBytes))
return return
} }
notification.Success("Sync Config Success", string(notificationPayloadBytes)) notification.Success("Sync Config Success", string(notificationPayloadBytes))
// handle rename // handle rename
if p.NewFilepath == "" || p.Filepath == p.NewFilepath { if p.NewFilepath == "" || p.Filepath == p.NewFilepath {
return return
} }
payload := &RenameConfigPayload{ payload := &RenameConfigPayload{
Filepath: p.Filepath, Filepath: p.Filepath,
NewFilepath: p.NewFilepath, NewFilepath: p.NewFilepath,
} }
err = payload.rename(env) err = payload.rename(env)
return return
} }
type RenameConfigPayload struct { type RenameConfigPayload struct {
Filepath string `json:"filepath"` Filepath string `json:"filepath"`
NewFilepath string `json:"new_filepath"` NewFilepath string `json:"new_filepath"`
} }
type SyncRenameNotificationPayload struct { type SyncRenameNotificationPayload struct {
StatusCode int `json:"status_code"` StatusCode int `json:"status_code"`
OrigPath string `json:"orig_path"` OrigPath string `json:"orig_path"`
NewPath string `json:"new_path"` NewPath string `json:"new_path"`
EnvName string `json:"env_name"` EnvName string `json:"env_name"`
RespBody string `json:"resp_body"` RespBody string `json:"resp_body"`
} }
func (p *RenameConfigPayload) rename(env *model.Environment) (err error) { func (p *RenameConfigPayload) rename(env *model.Environment) (err error) {
// handle rename // handle rename
if p.NewFilepath == "" || p.Filepath == p.NewFilepath { if p.NewFilepath == "" || p.Filepath == p.NewFilepath {
return return
} }
client := http.Client{ client := http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify}, TLSClientConfig: &tls.Config{InsecureSkipVerify: settings.ServerSettings.InsecureSkipVerify},
}, },
} }
payloadBytes, err := json.Marshal(gin.H{ payloadBytes, err := json.Marshal(gin.H{
"base_path": strings.ReplaceAll(filepath.Dir(p.Filepath), nginx.GetConfPath(), ""), "base_path": strings.ReplaceAll(filepath.Dir(p.Filepath), nginx.GetConfPath(), ""),
"orig_name": filepath.Base(p.Filepath), "orig_name": filepath.Base(p.Filepath),
"new_name": filepath.Base(p.NewFilepath), "new_name": filepath.Base(p.NewFilepath),
}) })
if err != nil { if err != nil {
return return
} }
url, err := env.GetUrl("/api/config_rename") url, err := env.GetUrl("/api/config_rename")
if err != nil { if err != nil {
return return
} }
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes)) req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes))
if err != nil { if err != nil {
return return
} }
req.Header.Set("X-Node-Secret", env.Token) req.Header.Set("X-Node-Secret", env.Token)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return return
} }
notificationPayload := &SyncRenameNotificationPayload{ notificationPayload := &SyncRenameNotificationPayload{
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
OrigPath: p.Filepath, OrigPath: p.Filepath,
NewPath: p.NewFilepath, NewPath: p.NewFilepath,
EnvName: env.Name, EnvName: env.Name,
RespBody: string(respBody), RespBody: string(respBody),
} }
notificationPayloadBytes, err := json.Marshal(notificationPayload) notificationPayloadBytes, err := json.Marshal(notificationPayload)
if err != nil { if err != nil {
return return
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
notification.Error("Rename Remote Config Error", string(notificationPayloadBytes)) notification.Error("Rename Remote Config Error", string(notificationPayloadBytes))
return return
} }
notification.Success("Rename Remote Config Success", string(notificationPayloadBytes)) notification.Success("Rename Remote Config Success", string(notificationPayloadBytes))
return return
} }