mirror of https://github.com/usual2970/certimate
				
				
				
			feat: backend support for ratpanel
							parent
							
								
									851ad70a6c
								
							
						
					
					
						commit
						bd4aa4806f
					
				| 
						 | 
				
			
			@ -57,6 +57,8 @@ import (
 | 
			
		|||
	pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
 | 
			
		||||
	pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
 | 
			
		||||
	pRainYunRCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/rainyun-rcdn"
 | 
			
		||||
	pRatPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ratpanel-console"
 | 
			
		||||
	pRatPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ratpanel-site"
 | 
			
		||||
	pSafeLine "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline"
 | 
			
		||||
	pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
 | 
			
		||||
	pTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn"
 | 
			
		||||
| 
						 | 
				
			
			@ -813,6 +815,38 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case domain.DeploymentProviderTypeRatPanelConsole, domain.DeploymentProviderTypeRatPanelSite:
 | 
			
		||||
		{
 | 
			
		||||
			access := domain.AccessConfigForRatPanel{}
 | 
			
		||||
			if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
 | 
			
		||||
				return nil, fmt.Errorf("failed to populate provider access config: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch options.Provider {
 | 
			
		||||
			case domain.DeploymentProviderTypeRatPanelConsole:
 | 
			
		||||
				deployer, err := pRatPanelConsole.NewDeployer(&pRatPanelConsole.DeployerConfig{
 | 
			
		||||
					ApiUrl:                   access.ApiUrl,
 | 
			
		||||
					AccessTokenId:            access.AccessTokenId,
 | 
			
		||||
					AccessToken:              access.AccessToken,
 | 
			
		||||
					AllowInsecureConnections: access.AllowInsecureConnections,
 | 
			
		||||
				})
 | 
			
		||||
				return deployer, err
 | 
			
		||||
 | 
			
		||||
			case domain.DeploymentProviderTypeRatPanelSite:
 | 
			
		||||
				deployer, err := pRatPanelSite.NewDeployer(&pRatPanelSite.DeployerConfig{
 | 
			
		||||
					ApiUrl:                   access.ApiUrl,
 | 
			
		||||
					AccessTokenId:            access.AccessTokenId,
 | 
			
		||||
					AccessToken:              access.AccessToken,
 | 
			
		||||
					AllowInsecureConnections: access.AllowInsecureConnections,
 | 
			
		||||
					SiteName:                 maputil.GetString(options.ProviderExtendedConfig, "siteName"),
 | 
			
		||||
				})
 | 
			
		||||
				return deployer, err
 | 
			
		||||
 | 
			
		||||
			default:
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case domain.DeploymentProviderTypeSafeLine:
 | 
			
		||||
		{
 | 
			
		||||
			access := domain.AccessConfigForSafeLine{}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -240,6 +240,13 @@ type AccessConfigForRainYun struct {
 | 
			
		|||
	ApiKey string `json:"apiKey"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccessConfigForRatPanel struct {
 | 
			
		||||
	ApiUrl                   string `json:"apiUrl"`
 | 
			
		||||
	AccessTokenId            uint   `json:"accessTokenId"`
 | 
			
		||||
	AccessToken              string `json:"accessToken"`
 | 
			
		||||
	AllowInsecureConnections bool   `json:"allowInsecureConnections,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccessConfigForSafeLine struct {
 | 
			
		||||
	ApiUrl                   string `json:"apiUrl"`
 | 
			
		||||
	ApiToken                 string `json:"apiToken"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,6 +61,7 @@ const (
 | 
			
		|||
	AccessProviderTypeQiniu               = AccessProviderType("qiniu")
 | 
			
		||||
	AccessProviderTypeQingCloud           = AccessProviderType("qingcloud") // 青云(预留)
 | 
			
		||||
	AccessProviderTypeRainYun             = AccessProviderType("rainyun")
 | 
			
		||||
	AccessProviderTypeRatPanel            = AccessProviderType("ratpanel")
 | 
			
		||||
	AccessProviderTypeSafeLine            = AccessProviderType("safeline")
 | 
			
		||||
	AccessProviderTypeSSH                 = AccessProviderType("ssh")
 | 
			
		||||
	AccessProviderTypeSSLCOM              = AccessProviderType("sslcom")
 | 
			
		||||
| 
						 | 
				
			
			@ -208,6 +209,8 @@ const (
 | 
			
		|||
	DeploymentProviderTypeQiniuKodo             = DeploymentProviderType(AccessProviderTypeQiniu + "-kodo")
 | 
			
		||||
	DeploymentProviderTypeQiniuPili             = DeploymentProviderType(AccessProviderTypeQiniu + "-pili")
 | 
			
		||||
	DeploymentProviderTypeRainYunRCDN           = DeploymentProviderType(AccessProviderTypeRainYun + "-rcdn")
 | 
			
		||||
	DeploymentProviderTypeRatPanelConsole       = DeploymentProviderType(AccessProviderTypeRatPanel + "-console")
 | 
			
		||||
	DeploymentProviderTypeRatPanelSite          = DeploymentProviderType(AccessProviderTypeRatPanel + "-site")
 | 
			
		||||
	DeploymentProviderTypeSafeLine              = DeploymentProviderType(AccessProviderTypeSafeLine)
 | 
			
		||||
	DeploymentProviderTypeSSH                   = DeploymentProviderType(AccessProviderTypeSSH)
 | 
			
		||||
	DeploymentProviderTypeTencentCloudCDN       = DeploymentProviderType(AccessProviderTypeTencentCloud + "-cdn")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
package baotapanelconsole
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"net/url"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/core/deployer"
 | 
			
		||||
	rpsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/ratpanel"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DeployerConfig struct {
 | 
			
		||||
	// 耗子面板地址。
 | 
			
		||||
	ApiUrl string `json:"apiUrl"`
 | 
			
		||||
	// 耗子面板访问令牌ID。
 | 
			
		||||
	AccessTokenId uint `json:"accessTokenId"`
 | 
			
		||||
	// 耗子面板访问令牌。
 | 
			
		||||
	AccessToken string `json:"accessToken"`
 | 
			
		||||
	// 是否允许不安全的连接。
 | 
			
		||||
	AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DeployerProvider struct {
 | 
			
		||||
	config    *DeployerConfig
 | 
			
		||||
	logger    *slog.Logger
 | 
			
		||||
	sdkClient *rpsdk.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
 | 
			
		||||
 | 
			
		||||
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
 | 
			
		||||
	if config == nil {
 | 
			
		||||
		panic("config is nil")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := createSdkClient(config.ApiUrl, config.AccessTokenId, config.AccessToken, config.AllowInsecureConnections)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create sdk client: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &DeployerProvider{
 | 
			
		||||
		config:    config,
 | 
			
		||||
		logger:    slog.Default(),
 | 
			
		||||
		sdkClient: client,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
 | 
			
		||||
	if logger == nil {
 | 
			
		||||
		d.logger = slog.Default()
 | 
			
		||||
	} else {
 | 
			
		||||
		d.logger = logger
 | 
			
		||||
	}
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
 | 
			
		||||
	// 设置面板 SSL 证书
 | 
			
		||||
	settingCertReq := &rpsdk.SettingCertRequest{
 | 
			
		||||
		Certificate: certPEM,
 | 
			
		||||
		PrivateKey:  privkeyPEM,
 | 
			
		||||
	}
 | 
			
		||||
	settingCertResp, err := d.sdkClient.SettingCert(settingCertReq)
 | 
			
		||||
	d.logger.Debug("sdk request 'ratpanel.SettingCertRequest'", slog.Any("request", settingCertReq), slog.Any("response", settingCertResp))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to execute sdk request 'ratpanel.SettingCertRequest': %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &deployer.DeployResult{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createSdkClient(apiUrl string, accessTokenId uint, accessToken string, skipTlsVerify bool) (*rpsdk.Client, error) {
 | 
			
		||||
	if _, err := url.Parse(apiUrl); err != nil {
 | 
			
		||||
		return nil, errors.New("invalid ratpanel api url")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if accessTokenId == 0 {
 | 
			
		||||
		return nil, errors.New("invalid ratpanel access token id")
 | 
			
		||||
	}
 | 
			
		||||
	if accessToken == "" {
 | 
			
		||||
		return nil, errors.New("invalid ratpanel access token")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := rpsdk.NewClient(apiUrl, accessTokenId, accessToken)
 | 
			
		||||
	if skipTlsVerify {
 | 
			
		||||
		client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
package baotapanelconsole_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	fInputCertPath string
 | 
			
		||||
	fInputKeyPath  string
 | 
			
		||||
	fApiUrl        string
 | 
			
		||||
	fApiKey        string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	argsPrefix := "CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_"
 | 
			
		||||
 | 
			
		||||
	flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
 | 
			
		||||
	flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
 | 
			
		||||
	flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
 | 
			
		||||
	flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Shell command to run this test:
 | 
			
		||||
 | 
			
		||||
	go test -v ./baotapanel_console_test.go -args \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_APIURL="http://127.0.0.1:8888" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELCONSOLE_APIKEY="your-api-key"
 | 
			
		||||
*/
 | 
			
		||||
func TestDeploy(t *testing.T) {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	t.Run("Deploy", func(t *testing.T) {
 | 
			
		||||
		t.Log(strings.Join([]string{
 | 
			
		||||
			"args:",
 | 
			
		||||
			fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
 | 
			
		||||
			fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
 | 
			
		||||
			fmt.Sprintf("APIURL: %v", fApiUrl),
 | 
			
		||||
			fmt.Sprintf("APIKEY: %v", fApiKey),
 | 
			
		||||
		}, "\n"))
 | 
			
		||||
 | 
			
		||||
		deployer, err := provider.NewDeployer(&provider.DeployerConfig{
 | 
			
		||||
			ApiUrl:                   fApiUrl,
 | 
			
		||||
			ApiKey:                   fApiKey,
 | 
			
		||||
			AllowInsecureConnections: true,
 | 
			
		||||
			AutoRestart:              true,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("err: %+v", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fInputCertData, _ := os.ReadFile(fInputCertPath)
 | 
			
		||||
		fInputKeyData, _ := os.ReadFile(fInputKeyPath)
 | 
			
		||||
		res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("err: %+v", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		t.Logf("ok: %v", res)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,100 @@
 | 
			
		|||
package baotapanelsite
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"net/url"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/core/deployer"
 | 
			
		||||
	rpsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/ratpanel"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DeployerConfig struct {
 | 
			
		||||
	// 耗子面板地址。
 | 
			
		||||
	ApiUrl string `json:"apiUrl"`
 | 
			
		||||
	// 耗子面板访问令牌ID。
 | 
			
		||||
	AccessTokenId uint `json:"accessTokenId"`
 | 
			
		||||
	// 耗子面板访问令牌。
 | 
			
		||||
	AccessToken string `json:"accessToken"`
 | 
			
		||||
	// 是否允许不安全的连接。
 | 
			
		||||
	AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
 | 
			
		||||
	// 网站名称。
 | 
			
		||||
	SiteName string `json:"siteName,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DeployerProvider struct {
 | 
			
		||||
	config    *DeployerConfig
 | 
			
		||||
	logger    *slog.Logger
 | 
			
		||||
	sdkClient *rpsdk.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ deployer.Deployer = (*DeployerProvider)(nil)
 | 
			
		||||
 | 
			
		||||
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
 | 
			
		||||
	if config == nil {
 | 
			
		||||
		panic("config is nil")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := createSdkClient(config.ApiUrl, config.AccessTokenId, config.AccessToken, config.AllowInsecureConnections)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create sdk client: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &DeployerProvider{
 | 
			
		||||
		config:    config,
 | 
			
		||||
		logger:    slog.Default(),
 | 
			
		||||
		sdkClient: client,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
 | 
			
		||||
	if logger == nil {
 | 
			
		||||
		d.logger = slog.Default()
 | 
			
		||||
	} else {
 | 
			
		||||
		d.logger = logger
 | 
			
		||||
	}
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
 | 
			
		||||
	if d.config.SiteName == "" {
 | 
			
		||||
		return nil, errors.New("config `siteName` is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置站点 SSL 证书
 | 
			
		||||
	websiteCertReq := &rpsdk.WebsiteCertRequest{
 | 
			
		||||
		SiteName:    d.config.SiteName,
 | 
			
		||||
		Certificate: certPEM,
 | 
			
		||||
		PrivateKey:  privkeyPEM,
 | 
			
		||||
	}
 | 
			
		||||
	websiteCertResp, err := d.sdkClient.WebsiteCert(websiteCertReq)
 | 
			
		||||
	d.logger.Debug("sdk request 'ratpanel.WebsiteCertRequest'", slog.Any("request", websiteCertReq), slog.Any("response", websiteCertResp))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to execute sdk request 'ratpanel.WebsiteCertRequest': %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &deployer.DeployResult{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createSdkClient(apiUrl string, accessTokenId uint, accessToken string, skipTlsVerify bool) (*rpsdk.Client, error) {
 | 
			
		||||
	if _, err := url.Parse(apiUrl); err != nil {
 | 
			
		||||
		return nil, errors.New("invalid ratpanel api url")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if accessTokenId == 0 {
 | 
			
		||||
		return nil, errors.New("invalid ratpanel access token id")
 | 
			
		||||
	}
 | 
			
		||||
	if accessToken == "" {
 | 
			
		||||
		return nil, errors.New("invalid ratpanel access token")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := rpsdk.NewClient(apiUrl, accessTokenId, accessToken)
 | 
			
		||||
	if skipTlsVerify {
 | 
			
		||||
		client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,82 @@
 | 
			
		|||
package baotapanelsite_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	fInputCertPath string
 | 
			
		||||
	fInputKeyPath  string
 | 
			
		||||
	fApiUrl        string
 | 
			
		||||
	fApiKey        string
 | 
			
		||||
	fSiteType      string
 | 
			
		||||
	fSiteName      string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	argsPrefix := "CERTIMATE_DEPLOYER_BAOTAPANELSITE_"
 | 
			
		||||
 | 
			
		||||
	flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
 | 
			
		||||
	flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
 | 
			
		||||
	flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
 | 
			
		||||
	flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
 | 
			
		||||
	flag.StringVar(&fSiteType, argsPrefix+"SITETYPE", "", "")
 | 
			
		||||
	flag.StringVar(&fSiteName, argsPrefix+"SITENAME", "", "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Shell command to run this test:
 | 
			
		||||
 | 
			
		||||
	go test -v ./baotapanel_site_test.go -args \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELSITE_APIURL="http://127.0.0.1:8888" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELSITE_APIKEY="your-api-key" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELSITE_SITETYPE="php" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_BAOTAPANELSITE_SITENAME="your-site-name"
 | 
			
		||||
*/
 | 
			
		||||
func TestDeploy(t *testing.T) {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	t.Run("Deploy", func(t *testing.T) {
 | 
			
		||||
		t.Log(strings.Join([]string{
 | 
			
		||||
			"args:",
 | 
			
		||||
			fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
 | 
			
		||||
			fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
 | 
			
		||||
			fmt.Sprintf("APIURL: %v", fApiUrl),
 | 
			
		||||
			fmt.Sprintf("APIKEY: %v", fApiKey),
 | 
			
		||||
			fmt.Sprintf("SITETYPE: %v", fSiteType),
 | 
			
		||||
			fmt.Sprintf("SITENAME: %v", fSiteName),
 | 
			
		||||
		}, "\n"))
 | 
			
		||||
 | 
			
		||||
		deployer, err := provider.NewDeployer(&provider.DeployerConfig{
 | 
			
		||||
			ApiUrl:                   fApiUrl,
 | 
			
		||||
			ApiKey:                   fApiKey,
 | 
			
		||||
			AllowInsecureConnections: true,
 | 
			
		||||
			SiteType:                 fSiteType,
 | 
			
		||||
			SiteName:                 fSiteName,
 | 
			
		||||
			SiteNames:                []string{fSiteName},
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("err: %+v", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fInputCertData, _ := os.ReadFile(fInputCertPath)
 | 
			
		||||
		fInputKeyData, _ := os.ReadFile(fInputKeyPath)
 | 
			
		||||
		res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("err: %+v", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		t.Logf("ok: %v", res)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
package btpanelsdk
 | 
			
		||||
 | 
			
		||||
import "net/http"
 | 
			
		||||
 | 
			
		||||
func (c *Client) SettingCert(req *SettingCertRequest) (*SettingCertResponse, error) {
 | 
			
		||||
	resp := &SettingCertResponse{}
 | 
			
		||||
	err := c.sendRequestWithResult(http.MethodPost, "/setting/cert", req, resp)
 | 
			
		||||
	return resp, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) WebsiteCert(req *SiteCertRequest) (*SiteCertResponse, error) {
 | 
			
		||||
	resp := &SiteCertResponse{}
 | 
			
		||||
	err := c.sendRequestWithResult(http.MethodPost, "/website/cert", req, resp)
 | 
			
		||||
	return resp, err
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,145 @@
 | 
			
		|||
package btpanelsdk
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/hmac"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-resty/resty/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Client struct {
 | 
			
		||||
	client *resty.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewClient(apiHost string, accessTokenId uint, accessToken string) *Client {
 | 
			
		||||
	client := resty.New().
 | 
			
		||||
		SetBaseURL(strings.TrimRight(apiHost, "/")+"/api").
 | 
			
		||||
		SetHeader("Accept", "application/json").
 | 
			
		||||
		SetHeader("Content-Type", "application/json").
 | 
			
		||||
		SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
 | 
			
		||||
			var body []byte
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			if req.Body != nil {
 | 
			
		||||
				body, err = io.ReadAll(req.Body)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				req.Body = io.NopCloser(bytes.NewReader(body))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			canonicalPath := req.URL.Path
 | 
			
		||||
			if !strings.HasPrefix(canonicalPath, "/api") {
 | 
			
		||||
				index := strings.Index(canonicalPath, "/api")
 | 
			
		||||
				if index != -1 {
 | 
			
		||||
					canonicalPath = canonicalPath[index:]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s",
 | 
			
		||||
				req.Method,
 | 
			
		||||
				canonicalPath,
 | 
			
		||||
				req.URL.Query().Encode(),
 | 
			
		||||
				sha256Sum(string(body)))
 | 
			
		||||
 | 
			
		||||
			timestamp := time.Now().Unix()
 | 
			
		||||
			req.Header.Set("X-Timestamp", fmt.Sprintf("%d", timestamp))
 | 
			
		||||
 | 
			
		||||
			stringToSign := fmt.Sprintf("%s\n%d\n%s",
 | 
			
		||||
				"HMAC-SHA256",
 | 
			
		||||
				timestamp,
 | 
			
		||||
				sha256Sum(canonicalRequest))
 | 
			
		||||
			signature := hmacSha256(stringToSign, accessToken)
 | 
			
		||||
			req.Header.Set("Authorization", fmt.Sprintf("HMAC-SHA256 Credential=%d, Signature=%s", accessTokenId, signature))
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
	return &Client{
 | 
			
		||||
		client: client,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) WithTimeout(timeout time.Duration) *Client {
 | 
			
		||||
	c.client.SetTimeout(timeout)
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) WithTLSConfig(config *tls.Config) *Client {
 | 
			
		||||
	c.client.SetTLSClientConfig(config)
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) {
 | 
			
		||||
	req := c.client.R()
 | 
			
		||||
	req.Method = method
 | 
			
		||||
	req.URL = path
 | 
			
		||||
	if strings.EqualFold(method, http.MethodGet) {
 | 
			
		||||
		qs := make(map[string]string)
 | 
			
		||||
		if params != nil {
 | 
			
		||||
			temp := make(map[string]any)
 | 
			
		||||
			jsonb, _ := json.Marshal(params)
 | 
			
		||||
			json.Unmarshal(jsonb, &temp)
 | 
			
		||||
			for k, v := range temp {
 | 
			
		||||
				if v != nil {
 | 
			
		||||
					qs[k] = fmt.Sprintf("%v", v)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		req = req.SetQueryParams(qs)
 | 
			
		||||
	} else {
 | 
			
		||||
		req = req.
 | 
			
		||||
			SetHeader("Content-Type", "application/json").
 | 
			
		||||
			SetBody(params)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp, err := req.Send()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return resp, fmt.Errorf("ratpanel api error: failed to send request: %w", err)
 | 
			
		||||
	} else if resp.IsError() {
 | 
			
		||||
		return resp, fmt.Errorf("ratpanel api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return resp, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error {
 | 
			
		||||
	resp, err := c.sendRequest(method, path, params)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if resp != nil {
 | 
			
		||||
			json.Unmarshal(resp.Body(), &result)
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = json.Unmarshal(resp.Body(), &result); err != nil {
 | 
			
		||||
		return fmt.Errorf("ratpanel api error: failed to parse response: %w", err)
 | 
			
		||||
	} else if errmessage := result.GetMessage(); errmessage != "success" {
 | 
			
		||||
		return fmt.Errorf("ratpanel api error: %d - %s", resp.StatusCode(), errmessage)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sha256Sum(str string) string {
 | 
			
		||||
	sum := sha256.Sum256([]byte(str))
 | 
			
		||||
	dst := make([]byte, hex.EncodedLen(len(sum)))
 | 
			
		||||
	hex.Encode(dst, sum[:])
 | 
			
		||||
	return string(dst)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func hmacSha256(data string, secret string) string {
 | 
			
		||||
	h := hmac.New(sha256.New, []byte(secret))
 | 
			
		||||
	h.Write([]byte(data))
 | 
			
		||||
	return hex.EncodeToString(h.Sum(nil))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
package btpanelsdk
 | 
			
		||||
 | 
			
		||||
type BaseResponse interface {
 | 
			
		||||
	GetMessage() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type baseResponse struct {
 | 
			
		||||
	Message *string `json:"msg,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *baseResponse) GetMessage() string {
 | 
			
		||||
	if r.Message != nil {
 | 
			
		||||
		return *r.Message
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SettingCertRequest struct {
 | 
			
		||||
	Certificate string `json:"cert"`
 | 
			
		||||
	PrivateKey  string `json:"key"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SettingCertResponse struct {
 | 
			
		||||
	baseResponse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type WebsiteCertRequest struct {
 | 
			
		||||
	SiteName    string `json:"name"`
 | 
			
		||||
	Certificate string `json:"cert"`
 | 
			
		||||
	PrivateKey  string `json:"key"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type WebsiteCertResponse struct {
 | 
			
		||||
	baseResponse
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue