feat(provider): add Proxmox Backup Server deploy target

This commit is contained in:
bangbang93
2026-08-08 21:04:49 +08:00
committed by GitHub
parent f94fa075c5
commit 1f5832386b
23 changed files with 604 additions and 11 deletions
@@ -0,0 +1,29 @@
package deployers
import (
"fmt"
"github.com/certimate-go/certimate/internal/domain"
"github.com/certimate-go/certimate/pkg/core"
dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/proxmoxbs"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
Registries.MustRegister(domain.DeploymentProviderTypeProxmoxBS, func(options *ProviderFactoryOptions) (core.Deployer, error) {
credentials := domain.AccessConfigForProxmoxBS{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
ServerUrl: credentials.ServerUrl,
ApiToken: credentials.ApiToken,
ApiTokenSecret: credentials.ApiTokenSecret,
AllowInsecureConnections: credentials.AllowInsecureConnections,
NodeName: xmaps.GetString(options.ProviderExtendedConfig, "nodeName"),
AutoRestart: xmaps.GetBool(options.ProviderExtendedConfig, "autoRestart"),
})
return provider, err
})
}
+7
View File
@@ -491,6 +491,13 @@ type AccessConfigForPowerDNS struct {
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForProxmoxBS struct {
ServerUrl string `json:"serverUrl"`
ApiToken string `json:"apiToken"`
ApiTokenSecret string `json:"apiTokenSecret,omitempty"`
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForProxmoxVE struct {
ServerUrl string `json:"serverUrl"`
ApiToken string `json:"apiToken"`
+2
View File
@@ -104,6 +104,7 @@ const (
AccessProviderTypeOVHcloud = AccessProviderType("ovhcloud")
AccessProviderTypePorkbun = AccessProviderType("porkbun")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
AccessProviderTypeProxmoxBS = AccessProviderType("proxmoxbs")
AccessProviderTypeProxmoxVE = AccessProviderType("proxmoxve")
AccessProviderTypeQiniu = AccessProviderType("qiniu")
AccessProviderTypeQingCloud = AccessProviderType("qingcloud")
@@ -413,6 +414,7 @@ const (
DeploymentProviderTypeNetlify = DeploymentProviderType(AccessProviderTypeNetlify)
DeploymentProviderTypeNginxProxyManager = DeploymentProviderType(AccessProviderTypeNginxProxyManager)
DeploymentProviderTypeOracleCloudCertificatesMgmt = DeploymentProviderType(AccessProviderTypeOracleCloud + "-certificatesmgmt")
DeploymentProviderTypeProxmoxBS = DeploymentProviderType(AccessProviderTypeProxmoxBS)
DeploymentProviderTypeProxmoxVE = DeploymentProviderType(AccessProviderTypeProxmoxVE)
DeploymentProviderTypeQingCloudLB = DeploymentProviderType(AccessProviderTypeQingCloud + "-lb")
DeploymentProviderTypeQiniuCDN = DeploymentProviderType(AccessProviderTypeQiniu + "-cdn")
@@ -0,0 +1,102 @@
package proxmoxbs
import (
"context"
"crypto/tls"
"fmt"
"log/slog"
"github.com/certimate-go/certimate/pkg/core"
pbssdk "github.com/certimate-go/certimate/pkg/sdk3rd/proxmoxbs"
)
type (
Provider = core.Deployer
DeployResult = core.DeployerDeployResult
)
type DeployerConfig struct {
// Proxmox BS 服务地址。
ServerUrl string `json:"serverUrl"`
// Proxmox BS API Token。
ApiToken string `json:"apiToken"`
// Proxmox BS API Token Secret。
ApiTokenSecret string `json:"apiTokenSecret,omitempty"`
// 是否允许不安全的连接。
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
// 节点名称。
NodeName string `json:"nodeName"`
// 是否自动重启。
AutoRestart bool `json:"autoRestart"`
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *pbssdk.Client
}
var _ Provider = (*Deployer)(nil)
func NewDeployer(config *DeployerConfig) (*Deployer, error) {
if config == nil {
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
}
client, err := createSDKClient(config.ServerUrl, config.ApiToken, config.ApiTokenSecret, config.AllowInsecureConnections)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
return &Deployer{
config: config,
logger: slog.Default(),
sdkClient: client,
}, nil
}
func (d *Deployer) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
if d.config.NodeName == "" {
return nil, fmt.Errorf("config `nodeName` is required")
}
// 上传自定义证书
// REF: https://pbs.proxmox.com/docs/api-viewer/index.html#/nodes/{node}/certificates/custom
nodeUploadCustomCertificateReq := &pbssdk.NodeUploadCustomCertificateRequest{
Certificates: certPEM,
Key: privkeyPEM,
Force: true,
Restart: d.config.AutoRestart,
}
nodeUploadCustomCertificateResp, err := d.sdkClient.NodeUploadCustomCertificateWithContext(ctx, d.config.NodeName, nodeUploadCustomCertificateReq)
d.logger.Debug("sdk request 'node.UploadCustomCertificate'", slog.String("params.node", d.config.NodeName), slog.Any("request", nodeUploadCustomCertificateReq), slog.Any("response", nodeUploadCustomCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'node.UploadCustomCertificate': %w", err)
}
return &DeployResult{}, nil
}
func createSDKClient(serverUrl, apiToken, apiTokenSecret string, skipTlsVerify bool) (*pbssdk.Client, error) {
client, err := pbssdk.NewClient(
serverUrl,
pbssdk.WithApiToken(apiToken, apiTokenSecret),
)
if err != nil {
return nil, err
}
if skipTlsVerify {
client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
}
return client, nil
}
@@ -0,0 +1,58 @@
package proxmoxbs_test
import (
"testing"
"github.com/stretchr/testify/require"
impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/proxmoxbs"
it "github.com/certimate-go/certimate/pkg/core/deployer/testing"
)
var (
fp = it.Args("PROXMOXBS_")
fTestCertPath string
fTestKeyPath string
fServerUrl string
fApiToken string
fApiTokenSecret string
fNodeName string
)
func init() {
fp.DefineString(&fTestCertPath, "TESTCERTPATH")
fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
fp.DefineString(&fServerUrl, "SERVERURL")
fp.DefineString(&fApiToken, "APITOKEN")
fp.DefineString(&fApiTokenSecret, "APITOKENSECRET")
fp.DefineString(&fNodeName, "NODENAME")
}
/*
Shell command to run this test:
go test -v ./proxmoxbs_test.go -args \
--PROXMOXBS_TESTCERTPATH="/path/to/your-test-cert.pem" \
--PROXMOXBS_TESTKEYPATH="/path/to/your-test-key.pem" \
--PROXMOXBS_SERVERURL="http://127.0.0.1:8007" \
--PROXMOXBS_APITOKEN="your-api-token" \
--PROXMOXBS_APITOKENSECRET="your-api-token-secret" \
--PROXMOXBS_NODENAME="your-node-name"
*/
func TestProvider(t *testing.T) {
fp.Parse()
t.Run("Deploy", func(t *testing.T) {
provider, err := impl.NewDeployer(&impl.DeployerConfig{
ServerUrl: fServerUrl,
ApiToken: fApiToken,
ApiTokenSecret: fApiTokenSecret,
AllowInsecureConnections: true,
NodeName: fNodeName,
AutoRestart: true,
})
require.NoError(t, err)
it.TestDeploy(t, provider, it.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
})
}
@@ -7,7 +7,7 @@ import (
"log/slog"
"github.com/certimate-go/certimate/pkg/core"
proxmoxvesdk "github.com/certimate-go/certimate/pkg/sdk3rd/proxmoxve"
pvesdk "github.com/certimate-go/certimate/pkg/sdk3rd/proxmoxve"
)
type (
@@ -33,7 +33,7 @@ type DeployerConfig struct {
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *proxmoxvesdk.Client
sdkClient *pvesdk.Client
}
var _ Provider = (*Deployer)(nil)
@@ -70,7 +70,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
// 上传自定义证书
// REF: https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node}/certificates/custom
nodeUploadCustomCertificateReq := &proxmoxvesdk.NodeUploadCustomCertificateRequest{
nodeUploadCustomCertificateReq := &pvesdk.NodeUploadCustomCertificateRequest{
Certificates: certPEM,
Key: privkeyPEM,
Force: true,
@@ -85,9 +85,10 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
return &DeployResult{}, nil
}
func createSDKClient(serverUrl, apiToken, apiTokenSecret string, skipTlsVerify bool) (*proxmoxvesdk.Client, error) {
client, err := proxmoxvesdk.NewClient(serverUrl,
proxmoxvesdk.WithApiToken(apiToken, apiTokenSecret),
func createSDKClient(serverUrl, apiToken, apiTokenSecret string, skipTlsVerify bool) (*pvesdk.Client, error) {
client, err := pvesdk.NewClient(
serverUrl,
pvesdk.WithApiToken(apiToken, apiTokenSecret),
)
if err != nil {
return nil, err
@@ -0,0 +1,47 @@
package proxmoxbs
import (
"context"
"fmt"
"net/http"
)
type NodeUploadCustomCertificateRequest CustomCertificate
type NodeUploadCustomCertificateResponse struct {
Data []struct {
FileName string `json:"filename,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Subject string `json:"subject,omitempty"`
Issuer string `json:"issuer,omitempty"`
SAN []string `json:"san,omitempty"`
NotAfter int64 `json:"notafter,omitempty"`
NotBefore int64 `json:"notbefore,omitempty"`
} `json:"data,omitempty"`
}
func (c *Client) NodeUploadCustomCertificate(node string, req *NodeUploadCustomCertificateRequest) (*NodeUploadCustomCertificateResponse, error) {
return c.NodeUploadCustomCertificateWithContext(context.Background(), node, req)
}
func (c *Client) NodeUploadCustomCertificateWithContext(ctx context.Context, node string, req *NodeUploadCustomCertificateRequest) (*NodeUploadCustomCertificateResponse, error) {
if node == "" {
return nil, fmt.Errorf("sdkerr: bad request: unset node")
}
path := fmt.Sprintf("/nodes/%s/certificates/custom", node)
httpreq, err := c.newRequest(http.MethodPost, path)
if err != nil {
return nil, err
} else {
httpreq.SetBody(req)
httpreq.SetContext(ctx)
}
result := &NodeUploadCustomCertificateResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}
+113
View File
@@ -0,0 +1,113 @@
// A simple SDK client for Proxmox Backup Server.
// API documentation: https://pbs.proxmox.com/docs/api-viewer/
package proxmoxbs
import (
"crypto/tls"
"encoding/json"
"fmt"
"net/url"
"strings"
"time"
"github.com/go-resty/resty/v2"
"github.com/certimate-go/certimate/internal/app"
)
type Client struct {
rc *resty.Client
}
func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
opts := &Options{}
for _, fn := range optFns {
fn(opts)
}
if serverUrl == "" {
return nil, fmt.Errorf("sdkerr: unset serverUrl")
}
if _, err := url.Parse(serverUrl); err != nil {
return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
}
if opts.TokenId == "" {
return nil, fmt.Errorf("sdkerr: unset token")
}
if opts.TokenSecret == "" {
return nil, fmt.Errorf("sdkerr: unset tokenSecret")
}
httper := resty.New().
SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/api2/json").
SetHeader("Accept", "application/json").
SetHeader("Authorization", fmt.Sprintf("PBSAPIToken=%s:%s", opts.TokenId, opts.TokenSecret)).
SetHeader("Content-Type", "application/json").
SetHeader("User-Agent", app.AppUserAgent)
return &Client{rc: httper}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.rc.SetTimeout(timeout)
return c
}
func (c *Client) SetTLSConfig(config *tls.Config) *Client {
c.rc.SetTLSClientConfig(config)
return c
}
func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
if method == "" {
return nil, fmt.Errorf("sdkerr: unset method")
}
if path == "" {
return nil, fmt.Errorf("sdkerr: unset path")
}
req := c.rc.R()
req.Method = method
req.URL = path
// WARN:
// DO NOT CALL `req.SetResult` or `req.SetError` AGAIN! USE `doRequestWithResult` INSTEAD.
return req, nil
}
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
}
return resp, nil
}
func (c *Client) doRequestWithResult(req *resty.Request, res any) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
resp, err := c.doRequest(req)
if err != nil {
if resp != nil {
json.Unmarshal(resp.Body(), &res)
}
return resp, err
}
if len(resp.Body()) != 0 {
if err := json.Unmarshal(resp.Body(), &res); err != nil {
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
}
}
return resp, nil
}
+8
View File
@@ -0,0 +1,8 @@
package proxmoxbs
type CustomCertificate struct {
Certificates string `json:"certificates,omitempty"`
Force bool `json:"force,omitempty"`
Key string `json:"key,omitempty"`
Restart bool `json:"restart,omitempty"`
}
+15
View File
@@ -0,0 +1,15 @@
package proxmoxbs
type Options struct {
TokenId string
TokenSecret string
}
type OptionsFunc func(*Options)
func WithApiToken(tokenId string, tokenSecret string) OptionsFunc {
return func(o *Options) {
o.TokenId = tokenId
o.TokenSecret = tokenSecret
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// A simple SDK client for Proxmox VE.
// A simple SDK client for Proxmox Virtual Environment.
// API documentation: https://pve.proxmox.com/pve-docs/api-viewer/index.html
package proxmoxve
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 1024 1024"><path fill="#e57000" d="M512 64 64 224v384c0 212 180 352 448 352s448-140 448-352V224zm0 128c123 0 224 39 224 86s-101 86-224 86-224-39-224-86 101-86 224-86m0 224c140 0 256-47 256-106v280c0 57-115 106-256 106s-256-49-256-106V310c0 59 116 106 256 106m0 86c-88 0-160 28-160 64s72 64 160 64 160-28 160-64-72-64-160-64"/></svg>

After

Width:  |  Height:  |  Size: 411 B

@@ -88,6 +88,7 @@ import AccessConfigFieldsProviderOracleCloud from "./AccessConfigFieldsProviderO
import AccessConfigFieldsProviderOVHcloud from "./AccessConfigFieldsProviderOVHcloud";
import AccessConfigFieldsProviderPorkbun from "./AccessConfigFieldsProviderPorkbun";
import AccessConfigFieldsProviderPowerDNS from "./AccessConfigFieldsProviderPowerDNS";
import AccessConfigFieldsProviderProxmoxBS from "./AccessConfigFieldsProviderProxmoxBS";
import AccessConfigFieldsProviderProxmoxVE from "./AccessConfigFieldsProviderProxmoxVE";
import AccessConfigFieldsProviderQingCloud from "./AccessConfigFieldsProviderQingCloud";
import AccessConfigFieldsProviderQiniu from "./AccessConfigFieldsProviderQiniu";
@@ -216,6 +217,7 @@ const providerComponentMap: Partial<Record<AccessProviderType, React.ComponentTy
[ACCESS_PROVIDERS.OVHCLOUD]: AccessConfigFieldsProviderOVHcloud,
[ACCESS_PROVIDERS.PORKBUN]: AccessConfigFieldsProviderPorkbun,
[ACCESS_PROVIDERS.POWERDNS]: AccessConfigFieldsProviderPowerDNS,
[ACCESS_PROVIDERS.PROXMOXBS]: AccessConfigFieldsProviderProxmoxBS,
[ACCESS_PROVIDERS.PROXMOXVE]: AccessConfigFieldsProviderProxmoxVE,
[ACCESS_PROVIDERS.QINGCLOUD]: AccessConfigFieldsProviderQingCloud,
[ACCESS_PROVIDERS.QINIU]: AccessConfigFieldsProviderQiniu,
@@ -0,0 +1,84 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const AccessConfigFormFieldsProviderProxmoxBS = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "serverUrl"]}
initialValue={initialValues.serverUrl}
label={t("access.form.proxmoxbs_server_url.label")}
rules={[formRule]}
>
<Input type="url" placeholder={t("access.form.proxmoxbs_server_url.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "apiToken"]}
initialValue={initialValues.apiToken}
label={t("access.form.proxmoxbs_api_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.proxmoxbs_api_token.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.proxmoxbs_api_token.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "apiTokenSecret"]}
initialValue={initialValues.apiTokenSecret}
label={t("access.form.proxmoxbs_api_token_secret.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.proxmoxbs_api_token_secret.tooltip") }}></span>}
>
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.proxmoxbs_api_token_secret.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "allowInsecureConnections"]}
initialValue={initialValues.allowInsecureConnections}
label={t("access.form.shared_allow_insecure_conns.label")}
rules={[formRule]}
>
<Switch />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
serverUrl: "http://<your-host-addr>:8007/",
apiToken: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t: _ } = i18n;
return z.object({
serverUrl: z.url({ protocol: z.core.regexes.httpProtocol }),
apiToken: z.string().nonempty(),
apiTokenSecret: z.string().nullish(),
allowInsecureConnections: z.boolean().nullish(),
});
};
const _default = Object.assign(AccessConfigFormFieldsProviderProxmoxBS, {
getInitialValues,
getSchema,
});
export default _default;
@@ -90,6 +90,7 @@ import BizDeployNodeConfigFieldsProviderMohuaMVH from "./BizDeployNodeConfigFiel
import BizDeployNodeConfigFieldsProviderNetlify from "./BizDeployNodeConfigFieldsProviderNetlify";
import BizDeployNodeConfigFieldsProviderNginxProxyManager from "./BizDeployNodeConfigFieldsProviderNginxProxyManager";
import BizDeployNodeConfigFieldsProviderOracleCloudCertificatesMgmt from "./BizDeployNodeConfigFieldsProviderOracleCloudCertificatesMgmt";
import BizDeployNodeConfigFieldsProviderProxmoxBS from "./BizDeployNodeConfigFieldsProviderProxmoxBS";
import BizDeployNodeConfigFieldsProviderProxmoxVE from "./BizDeployNodeConfigFieldsProviderProxmoxVE";
import BizDeployNodeConfigFieldsProviderQingCloudLB from "./BizDeployNodeConfigFieldsProviderQingCloudLB";
import BizDeployNodeConfigFieldsProviderQiniuCDN from "./BizDeployNodeConfigFieldsProviderQiniuCDN";
@@ -241,6 +242,7 @@ const providerComponentMap: Partial<Record<DeploymentProviderType, React.Compone
[DEPLOYMENT_PROVIDERS.NETLIFY]: BizDeployNodeConfigFieldsProviderNetlify,
[DEPLOYMENT_PROVIDERS.NGINXPROXYMANAGER]: BizDeployNodeConfigFieldsProviderNginxProxyManager,
[DEPLOYMENT_PROVIDERS.ORACLECLOUD_CERTIFICATESMGMT]: BizDeployNodeConfigFieldsProviderOracleCloudCertificatesMgmt,
[DEPLOYMENT_PROVIDERS.PROXMOXBS]: BizDeployNodeConfigFieldsProviderProxmoxBS,
[DEPLOYMENT_PROVIDERS.PROXMOXVE]: BizDeployNodeConfigFieldsProviderProxmoxVE,
[DEPLOYMENT_PROVIDERS.QINGCLOUD_LB]: BizDeployNodeConfigFieldsProviderQingCloudLB,
[DEPLOYMENT_PROVIDERS.QINIU_CDN]: BizDeployNodeConfigFieldsProviderQiniuCDN,
@@ -0,0 +1,68 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import Tips from "@/components/Tips";
import { useFormNestedFieldsContext } from "./_context";
const BizDeployNodeConfigFieldsProviderProxmoxBS = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item>
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.proxmoxbs.guide") }}></span>} />
</Form.Item>
<Form.Item
name={[parentNamePath, "nodeName"]}
initialValue={initialValues.nodeName}
label={t("workflow_node.deploy.form.proxmoxbs_node_name.label")}
rules={[formRule]}
>
<Input placeholder={t("workflow_node.deploy.form.proxmoxbs_node_name.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "autoRestart"]}
initialValue={initialValues.autoRestart}
label={t("workflow_node.deploy.form.proxmoxbs_auto_restart.label")}
rules={[formRule]}
>
<Switch />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
nodeName: "",
autoRestart: true,
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t: _ } = i18n;
return z.object({
nodeName: z.string().nonempty(),
autoRestart: z.boolean().nullish(),
});
};
const _default = Object.assign(BizDeployNodeConfigFieldsProviderProxmoxBS, {
getInitialValues,
getSchema,
});
export default _default;
+4
View File
@@ -106,6 +106,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
OVHCLOUD: "ovhcloud",
PORKBUN: "porkbun",
POWERDNS: "powerdns",
PROXMOXBS: "proxmoxbs",
PROXMOXVE: "proxmoxve",
QINGCLOUD: "qingcloud",
QINIU: "qiniu",
@@ -223,6 +224,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.KONG, "provider.kong", "/imgs/providers/kong.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.LECDN, "provider.lecdn", "/imgs/providers/lecdn.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.NGINXPROXYMANAGER, "provider.nginxproxymanager", "/imgs/providers/nginxproxymanager.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.PROXMOXBS, "provider.proxmoxbs", "/imgs/providers/proxmoxbs.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.PROXMOXVE, "provider.proxmoxve", "/imgs/providers/proxmoxve.svg", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.RATPANEL, "provider.ratpanel", "/imgs/providers/ratpanel.png", [ACCESS_USAGES.HOSTING]],
[ACCESS_PROVIDERS.SAFELINE, "provider.safeline", "/imgs/providers/safeline.svg", [ACCESS_USAGES.HOSTING]],
@@ -708,6 +710,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
NETLIFY: `${ACCESS_PROVIDERS.NETLIFY}`,
NGINXPROXYMANAGER: `${ACCESS_PROVIDERS.NGINXPROXYMANAGER}`,
ORACLECLOUD_CERTIFICATESMGMT: `${ACCESS_PROVIDERS.ORACLECLOUD}-certificatesmgmt`,
PROXMOXBS: `${ACCESS_PROVIDERS.PROXMOXBS}`,
PROXMOXVE: `${ACCESS_PROVIDERS.PROXMOXVE}`,
QINGCLOUD_LB: `${ACCESS_PROVIDERS.QINGCLOUD}-lb`,
QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`,
@@ -954,6 +957,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.CPANEL, "provider.cpanel", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.DOKPLOY, "provider.dokploy", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.NGINXPROXYMANAGER, "provider.nginxproxymanager", DEPLOYMENT_CATEGORIES.WEBSITE],
[DEPLOYMENT_PROVIDERS.PROXMOXBS, "provider.proxmoxbs", DEPLOYMENT_CATEGORIES.OTHER],
[DEPLOYMENT_PROVIDERS.PROXMOXVE, "provider.proxmoxve", DEPLOYMENT_CATEGORIES.OTHER],
[DEPLOYMENT_PROVIDERS.SYNOLOGYDSM, "provider.synologydsm", DEPLOYMENT_CATEGORIES.OTHER],
] satisfies Array<[DeploymentProviderType, string, DeploymentCategoryType, "builtin"] | [DeploymentProviderType, string, DeploymentCategoryType]>
+14
View File
@@ -1136,6 +1136,20 @@
"placeholder": "Please enter Proxmox VE API token secret",
"tooltip": "For more information, see <a href=\"https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens\" target=\"_blank\">https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens</a>"
},
"proxmoxbs_server_url": {
"label": "Proxmox BS server URL",
"placeholder": "Please enter Proxmox BS server URL"
},
"proxmoxbs_api_token": {
"label": "Proxmox BS API token",
"placeholder": "Please enter Proxmox BS API token",
"tooltip": "For more information, see <a href=\"https://pbs.proxmox.com/docs/user-management.html#api-tokens\" target=\"_blank\">https://pbs.proxmox.com/docs/user-management.html#api-tokens</a>"
},
"proxmoxbs_api_token_secret": {
"label": "Proxmox BS API token secret (Optional)",
"placeholder": "Please enter Proxmox BS API token secret",
"tooltip": "For more information, see <a href=\"https://pbs.proxmox.com/docs/user-management.html#api-tokens\" target=\"_blank\">https://pbs.proxmox.com/docs/user-management.html#api-tokens</a>"
},
"qingcloud_access_key_id": {
"label": "QingCloud AccessKeyID",
"placeholder": "Please enter QingCloud AccessKeyID",
+2 -1
View File
@@ -213,7 +213,8 @@
"ovhcloud": "OVHcloud",
"porkbun": "Porkbun",
"powerdns": "PowerDNS",
"proxmoxve": "Proxmox VE",
"proxmoxve": "PVE (Proxmox Virtual Environment)",
"proxmoxbs": "PBS (Proxmox Backup Server)",
"qingcloud": "QingCloud",
"qingcloud_dns": "QingCloud - DNS",
"qingcloud_lb": "QingCloud - LBC (Load Balancer Cluster)",
@@ -2020,7 +2020,7 @@
"tooltip": "For more information, see <a href=\"https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/managingcompartments.htm\" target=\"_blank\">https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/managingcompartments.htm</a>"
},
"proxmoxve": {
"guide": "Requires Proxmox VE v5.2 or higher."
"guide": "Requires Proxmox Virtual Environment v5.2 or higher."
},
"proxmoxve_node_name": {
"label": "Proxmox VE cluster node name",
@@ -2029,6 +2029,16 @@
"proxmoxve_auto_restart": {
"label": "Auto restart Proxmox VE after deployment"
},
"proxmoxbs": {
"guide": "Requires Proxmox Backup Server v2.1 or higher."
},
"proxmoxbs_node_name": {
"label": "Proxmox BS node name",
"placeholder": "Please enter Proxmox BS node name"
},
"proxmoxbs_auto_restart": {
"label": "Auto restart Proxmox BS after deployment"
},
"qingcloud_lb_zone_id": {
"label": "QingCloud zone ID",
"placeholder": "Please enter QingCloud LBC zone ID (e.g. pek3a)",
+14
View File
@@ -1136,6 +1136,20 @@
"placeholder": "请输入 Proxmox VE API Token Secret",
"tooltip": "这是什么?请参阅 <a href=\"https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens\" target=\"_blank\">https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens</a>"
},
"proxmoxbs_server_url": {
"label": "Proxmox BS 服务地址",
"placeholder": "请输入 Proxmox BS 服务地址"
},
"proxmoxbs_api_token": {
"label": "Proxmox BS API Token",
"placeholder": "请输入 Proxmox BS API Token",
"tooltip": "这是什么?请参阅 <a href=\"https://pbs.proxmox.com/docs/user-management.html#api-tokens\" target=\"_blank\">https://pbs.proxmox.com/docs/user-management.html#api-tokens</a>"
},
"proxmoxbs_api_token_secret": {
"label": "Proxmox BS API Token Secret(可选)",
"placeholder": "请输入 Proxmox BS API Token Secret",
"tooltip": "这是什么?请参阅 <a href=\"https://pbs.proxmox.com/docs/user-management.html#api-tokens\" target=\"_blank\">https://pbs.proxmox.com/docs/user-management.html#api-tokens</a>"
},
"qingcloud_access_key_id": {
"label": "青云 AccessKeyID",
"placeholder": "请输入青云 AccessKeyID",
+2 -1
View File
@@ -213,7 +213,8 @@
"ovhcloud": "OVHcloud",
"porkbun": "Porkbun",
"powerdns": "PowerDNS",
"proxmoxve": "Proxmox VE",
"proxmoxve": "PVEProxmox Virtual Environment",
"proxmoxbs": "PBSProxmox Backup Server",
"qingcloud": "青云",
"qingcloud_dns": "青云 - 云解析 DNS",
"qingcloud_lb": "青云 - 负载均衡 LB",
@@ -2019,7 +2019,7 @@
"tooltip": "这是什么?请参阅 <a href=\"https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/managingcompartments.htm\" target=\"_blank\">https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/managingcompartments.htm</a>"
},
"proxmoxve": {
"guide": "需要 Proxmox VE v5.2 或更高版本。"
"guide": "需要 Proxmox Virtual Environment v5.2 或更高版本。"
},
"proxmoxve_node_name": {
"label": "Proxmox VE 集群节点名称",
@@ -2028,6 +2028,16 @@
"proxmoxve_auto_restart": {
"label": "部署后自动重启 Proxmox VE 服务"
},
"proxmoxbs": {
"guide": "需要 Proxmox Backup Server v2.1 或更高版本。"
},
"proxmoxbs_node_name": {
"label": "Proxmox BS 节点名称",
"placeholder": "请输入 Proxmox BS 节点名称"
},
"proxmoxbs_auto_restart": {
"label": "部署后自动重启 Proxmox BS 服务"
},
"qingcloud_lb_zone_id": {
"label": "青云服务区域 ID",
"placeholder": "请输入青云 ELB 服务区域 ID(例如:pek3a",