mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 20:30:51 +08:00
feat(remote-management): add support for custom GitHub repository for panel updates
Introduce `panel-github-repository` in the configuration to allow specifying a custom repository for management panel assets. Update dependency versions and enhance asset URL resolution logic to support overrides.
This commit is contained in:
@@ -25,6 +25,9 @@ remote-management:
|
|||||||
# Disable the bundled management control panel asset download and HTTP route when true.
|
# Disable the bundled management control panel asset download and HTTP route when true.
|
||||||
disable-control-panel: false
|
disable-control-panel: false
|
||||||
|
|
||||||
|
# GitHub repository for the management control panel. Accepts a repository URL or releases API URL.
|
||||||
|
panel-github-repository: "https://github.com/router-for-me/Cli-Proxy-API-Management-Center"
|
||||||
|
|
||||||
# Authentication directory (supports ~ for home directory)
|
# Authentication directory (supports ~ for home directory)
|
||||||
auth-dir: "~/.cli-proxy-api"
|
auth-dir: "~/.cli-proxy-api"
|
||||||
|
|
||||||
|
|||||||
10
go.mod
10
go.mod
@@ -18,8 +18,8 @@ require (
|
|||||||
github.com/tidwall/gjson v1.18.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
github.com/tidwall/sjson v1.2.5
|
github.com/tidwall/sjson v1.2.5
|
||||||
github.com/tiktoken-go/tokenizer v0.7.0
|
github.com/tiktoken-go/tokenizer v0.7.0
|
||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.45.0
|
||||||
golang.org/x/net v0.46.0
|
golang.org/x/net v0.47.0
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@@ -68,9 +68,9 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
golang.org/x/text v0.30.0 // indirect
|
golang.org/x/text v0.31.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
24
go.sum
24
go.sum
@@ -160,22 +160,22 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
|
|||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
|||||||
@@ -609,7 +609,7 @@ func (s *Server) serveManagementControlPanel(c *gin.Context) {
|
|||||||
|
|
||||||
if _, err := os.Stat(filePath); err != nil {
|
if _, err := os.Stat(filePath); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
go managementasset.EnsureLatestManagementHTML(context.Background(), managementasset.StaticDir(s.configFilePath), cfg.ProxyURL)
|
go managementasset.EnsureLatestManagementHTML(context.Background(), managementasset.StaticDir(s.configFilePath), cfg.ProxyURL, cfg.RemoteManagement.PanelGitHubRepository)
|
||||||
c.AbortWithStatus(http.StatusNotFound)
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -929,7 +929,7 @@ func (s *Server) UpdateClients(cfg *config.Config) {
|
|||||||
|
|
||||||
if !cfg.RemoteManagement.DisableControlPanel {
|
if !cfg.RemoteManagement.DisableControlPanel {
|
||||||
staticDir := managementasset.StaticDir(s.configFilePath)
|
staticDir := managementasset.StaticDir(s.configFilePath)
|
||||||
go managementasset.EnsureLatestManagementHTML(context.Background(), staticDir, cfg.ProxyURL)
|
go managementasset.EnsureLatestManagementHTML(context.Background(), staticDir, cfg.ProxyURL, cfg.RemoteManagement.PanelGitHubRepository)
|
||||||
}
|
}
|
||||||
if s.mgmt != nil {
|
if s.mgmt != nil {
|
||||||
s.mgmt.SetConfig(cfg)
|
s.mgmt.SetConfig(cfg)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DefaultPanelGitHubRepository = "https://github.com/router-for-me/Cli-Proxy-API-Management-Center"
|
||||||
|
|
||||||
// Config represents the application's configuration, loaded from a YAML file.
|
// Config represents the application's configuration, loaded from a YAML file.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
config.SDKConfig `yaml:",inline"`
|
config.SDKConfig `yaml:",inline"`
|
||||||
@@ -104,6 +106,9 @@ type RemoteManagement struct {
|
|||||||
SecretKey string `yaml:"secret-key"`
|
SecretKey string `yaml:"secret-key"`
|
||||||
// DisableControlPanel skips serving and syncing the bundled management UI when true.
|
// DisableControlPanel skips serving and syncing the bundled management UI when true.
|
||||||
DisableControlPanel bool `yaml:"disable-control-panel"`
|
DisableControlPanel bool `yaml:"disable-control-panel"`
|
||||||
|
// PanelGitHubRepository overrides the GitHub repository used to fetch the management panel asset.
|
||||||
|
// Accepts either a repository URL (https://github.com/org/repo) or an API releases endpoint.
|
||||||
|
PanelGitHubRepository string `yaml:"panel-github-repository"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaExceeded defines the behavior when API quota limits are exceeded.
|
// QuotaExceeded defines the behavior when API quota limits are exceeded.
|
||||||
@@ -328,6 +333,7 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
|||||||
cfg.UsageStatisticsEnabled = false
|
cfg.UsageStatisticsEnabled = false
|
||||||
cfg.DisableCooling = false
|
cfg.DisableCooling = false
|
||||||
cfg.AmpCode.RestrictManagementToLocalhost = false // Default to false: API key auth is sufficient
|
cfg.AmpCode.RestrictManagementToLocalhost = false // Default to false: API key auth is sufficient
|
||||||
|
cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository
|
||||||
if err = yaml.Unmarshal(data, &cfg); err != nil {
|
if err = yaml.Unmarshal(data, &cfg); err != nil {
|
||||||
if optional {
|
if optional {
|
||||||
// In cloud deploy mode, if YAML parsing fails, return empty config instead of error.
|
// In cloud deploy mode, if YAML parsing fails, return empty config instead of error.
|
||||||
@@ -363,6 +369,11 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
|||||||
_ = SaveConfigPreserveCommentsUpdateNestedScalar(configFile, []string{"remote-management", "secret-key"}, hashed)
|
_ = SaveConfigPreserveCommentsUpdateNestedScalar(configFile, []string{"remote-management", "secret-key"}, hashed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg.RemoteManagement.PanelGitHubRepository = strings.TrimSpace(cfg.RemoteManagement.PanelGitHubRepository)
|
||||||
|
if cfg.RemoteManagement.PanelGitHubRepository == "" {
|
||||||
|
cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository
|
||||||
|
}
|
||||||
|
|
||||||
// Sync request authentication providers with inline API keys for backwards compatibility.
|
// Sync request authentication providers with inline API keys for backwards compatibility.
|
||||||
syncInlineAccessProvider(&cfg)
|
syncInlineAccessProvider(&cfg)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -23,10 +24,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
managementReleaseURL = "https://api.github.com/repos/router-for-me/Cli-Proxy-API-Management-Center/releases/latest"
|
defaultManagementReleaseURL = "https://api.github.com/repos/router-for-me/Cli-Proxy-API-Management-Center/releases/latest"
|
||||||
managementAssetName = "management.html"
|
managementAssetName = "management.html"
|
||||||
httpUserAgent = "CLIProxyAPI-management-updater"
|
httpUserAgent = "CLIProxyAPI-management-updater"
|
||||||
updateCheckInterval = 3 * time.Hour
|
updateCheckInterval = 3 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
// ManagementFileName exposes the control panel asset filename.
|
// ManagementFileName exposes the control panel asset filename.
|
||||||
@@ -97,7 +98,7 @@ func runAutoUpdater(ctx context.Context) {
|
|||||||
|
|
||||||
configPath, _ := schedulerConfigPath.Load().(string)
|
configPath, _ := schedulerConfigPath.Load().(string)
|
||||||
staticDir := StaticDir(configPath)
|
staticDir := StaticDir(configPath)
|
||||||
EnsureLatestManagementHTML(ctx, staticDir, cfg.ProxyURL)
|
EnsureLatestManagementHTML(ctx, staticDir, cfg.ProxyURL, cfg.RemoteManagement.PanelGitHubRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
runOnce()
|
runOnce()
|
||||||
@@ -181,7 +182,7 @@ func FilePath(configFilePath string) string {
|
|||||||
// EnsureLatestManagementHTML checks the latest management.html asset and updates the local copy when needed.
|
// EnsureLatestManagementHTML checks the latest management.html asset and updates the local copy when needed.
|
||||||
// The function is designed to run in a background goroutine and will never panic.
|
// The function is designed to run in a background goroutine and will never panic.
|
||||||
// It enforces a 3-hour rate limit to avoid frequent checks on config/auth file changes.
|
// It enforces a 3-hour rate limit to avoid frequent checks on config/auth file changes.
|
||||||
func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL string) {
|
func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL string, panelRepository string) {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
@@ -214,6 +215,7 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
releaseURL := resolveReleaseURL(panelRepository)
|
||||||
client := newHTTPClient(proxyURL)
|
client := newHTTPClient(proxyURL)
|
||||||
|
|
||||||
localPath := filepath.Join(staticDir, managementAssetName)
|
localPath := filepath.Join(staticDir, managementAssetName)
|
||||||
@@ -225,7 +227,7 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL
|
|||||||
localHash = ""
|
localHash = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
asset, remoteHash, err := fetchLatestAsset(ctx, client)
|
asset, remoteHash, err := fetchLatestAsset(ctx, client, releaseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Warn("failed to fetch latest management release information")
|
log.WithError(err).Warn("failed to fetch latest management release information")
|
||||||
return
|
return
|
||||||
@@ -254,8 +256,44 @@ func EnsureLatestManagementHTML(ctx context.Context, staticDir string, proxyURL
|
|||||||
log.Infof("management asset updated successfully (hash=%s)", downloadedHash)
|
log.Infof("management asset updated successfully (hash=%s)", downloadedHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchLatestAsset(ctx context.Context, client *http.Client) (*releaseAsset, string, error) {
|
func resolveReleaseURL(repo string) string {
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, managementReleaseURL, nil)
|
repo = strings.TrimSpace(repo)
|
||||||
|
if repo == "" {
|
||||||
|
return defaultManagementReleaseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := url.Parse(repo)
|
||||||
|
if err != nil || parsed.Host == "" {
|
||||||
|
return defaultManagementReleaseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
host := strings.ToLower(parsed.Host)
|
||||||
|
parsed.Path = strings.TrimSuffix(parsed.Path, "/")
|
||||||
|
|
||||||
|
if host == "api.github.com" {
|
||||||
|
if !strings.HasSuffix(strings.ToLower(parsed.Path), "/releases/latest") {
|
||||||
|
parsed.Path = parsed.Path + "/releases/latest"
|
||||||
|
}
|
||||||
|
return parsed.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if host == "github.com" {
|
||||||
|
parts := strings.Split(strings.Trim(parsed.Path, "/"), "/")
|
||||||
|
if len(parts) >= 2 && parts[0] != "" && parts[1] != "" {
|
||||||
|
repoName := strings.TrimSuffix(parts[1], ".git")
|
||||||
|
return fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", parts[0], repoName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultManagementReleaseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchLatestAsset(ctx context.Context, client *http.Client, releaseURL string) (*releaseAsset, string, error) {
|
||||||
|
if strings.TrimSpace(releaseURL) == "" {
|
||||||
|
releaseURL = defaultManagementReleaseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, releaseURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("create release request: %w", err)
|
return nil, "", fmt.Errorf("create release request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1882,6 +1882,11 @@ func buildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
|
|||||||
if oldCfg.RemoteManagement.DisableControlPanel != newCfg.RemoteManagement.DisableControlPanel {
|
if oldCfg.RemoteManagement.DisableControlPanel != newCfg.RemoteManagement.DisableControlPanel {
|
||||||
changes = append(changes, fmt.Sprintf("remote-management.disable-control-panel: %t -> %t", oldCfg.RemoteManagement.DisableControlPanel, newCfg.RemoteManagement.DisableControlPanel))
|
changes = append(changes, fmt.Sprintf("remote-management.disable-control-panel: %t -> %t", oldCfg.RemoteManagement.DisableControlPanel, newCfg.RemoteManagement.DisableControlPanel))
|
||||||
}
|
}
|
||||||
|
oldPanelRepo := strings.TrimSpace(oldCfg.RemoteManagement.PanelGitHubRepository)
|
||||||
|
newPanelRepo := strings.TrimSpace(newCfg.RemoteManagement.PanelGitHubRepository)
|
||||||
|
if oldPanelRepo != newPanelRepo {
|
||||||
|
changes = append(changes, fmt.Sprintf("remote-management.panel-github-repository: %s -> %s", oldPanelRepo, newPanelRepo))
|
||||||
|
}
|
||||||
if oldCfg.RemoteManagement.SecretKey != newCfg.RemoteManagement.SecretKey {
|
if oldCfg.RemoteManagement.SecretKey != newCfg.RemoteManagement.SecretKey {
|
||||||
switch {
|
switch {
|
||||||
case oldCfg.RemoteManagement.SecretKey == "" && newCfg.RemoteManagement.SecretKey != "":
|
case oldCfg.RemoteManagement.SecretKey == "" && newCfg.RemoteManagement.SecretKey != "":
|
||||||
|
|||||||
Reference in New Issue
Block a user