mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-17 01:49:52 +00:00
Merge pull request #426 from fudiwei/feat/new-workflow
feat: support ARI
This commit is contained in:
commit
101d55bafa
@ -14,10 +14,11 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/slices"
|
uslices "github.com/usual2970/certimate/internal/pkg/utils/slices"
|
||||||
"github.com/usual2970/certimate/internal/repository"
|
"github.com/usual2970/certimate/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,7 +46,9 @@ type applicantOptions struct {
|
|||||||
DnsPropagationTimeout int32
|
DnsPropagationTimeout int32
|
||||||
DnsTTL int32
|
DnsTTL int32
|
||||||
DisableFollowCNAME bool
|
DisableFollowCNAME bool
|
||||||
|
DisableARI bool
|
||||||
SkipBeforeExpiryDays int32
|
SkipBeforeExpiryDays int32
|
||||||
|
ReplacedARICertId string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
||||||
@ -54,32 +57,49 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nodeConfig := node.GetConfigForApply()
|
nodeConfig := node.GetConfigForApply()
|
||||||
|
|
||||||
accessRepo := repository.NewAccessRepository()
|
|
||||||
access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
accessConfig, err := access.UnmarshalConfigToMap()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
options := &applicantOptions{
|
options := &applicantOptions{
|
||||||
Domains: slices.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
|
Domains: uslices.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
|
||||||
ContactEmail: nodeConfig.ContactEmail,
|
ContactEmail: nodeConfig.ContactEmail,
|
||||||
Provider: domain.ApplyDNSProviderType(nodeConfig.Provider),
|
Provider: domain.ApplyDNSProviderType(nodeConfig.Provider),
|
||||||
ProviderAccessConfig: accessConfig,
|
|
||||||
ProviderApplyConfig: nodeConfig.ProviderConfig,
|
ProviderApplyConfig: nodeConfig.ProviderConfig,
|
||||||
KeyAlgorithm: nodeConfig.KeyAlgorithm,
|
KeyAlgorithm: nodeConfig.KeyAlgorithm,
|
||||||
Nameservers: slices.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
|
Nameservers: uslices.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
|
||||||
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
|
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
|
||||||
DnsTTL: nodeConfig.DnsTTL,
|
DnsTTL: nodeConfig.DnsTTL,
|
||||||
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
|
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
|
||||||
|
DisableARI: nodeConfig.DisableARI,
|
||||||
SkipBeforeExpiryDays: nodeConfig.SkipBeforeExpiryDays,
|
SkipBeforeExpiryDays: nodeConfig.SkipBeforeExpiryDays,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessRepo := repository.NewAccessRepository()
|
||||||
|
if access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
|
||||||
|
} else {
|
||||||
|
accessConfig, err := access.UnmarshalConfigToMap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
options.ProviderAccessConfig = accessConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
certRepo := repository.NewCertificateRepository()
|
||||||
|
lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), node.Id)
|
||||||
|
if lastCertificate != nil {
|
||||||
|
newCertSan := slices.Clone(options.Domains)
|
||||||
|
oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";")
|
||||||
|
slices.Sort(newCertSan)
|
||||||
|
slices.Sort(oldCertSan)
|
||||||
|
|
||||||
|
if slices.Equal(newCertSan, oldCertSan) {
|
||||||
|
lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate))
|
||||||
|
if lastCertX509 != nil {
|
||||||
|
replacedARICertId, _ := certificate.MakeARICertID(lastCertX509)
|
||||||
|
options.ReplacedARICertId = replacedARICertId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
applicant, err := createApplicant(options)
|
applicant, err := createApplicant(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -109,7 +129,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
|||||||
sslProviderConfig.Provider = defaultSSLProvider
|
sslProviderConfig.Provider = defaultSSLProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
myUser, err := newAcmeUser(sslProviderConfig.Provider, options.ContactEmail)
|
acmeUser, err := newAcmeUser(sslProviderConfig.Provider, options.ContactEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -118,39 +138,42 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
|||||||
// link: https://github.com/go-acme/lego/issues/1867
|
// link: https://github.com/go-acme/lego/issues/1867
|
||||||
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(options.DisableFollowCNAME))
|
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(options.DisableFollowCNAME))
|
||||||
|
|
||||||
config := lego.NewConfig(myUser)
|
// Create an ACME client config
|
||||||
|
config := lego.NewConfig(acmeUser)
|
||||||
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
|
|
||||||
config.CADirURL = sslProviderUrls[sslProviderConfig.Provider]
|
config.CADirURL = sslProviderUrls[sslProviderConfig.Provider]
|
||||||
config.Certificate.KeyType = parseKeyAlgorithm(options.KeyAlgorithm)
|
config.Certificate.KeyType = parseKeyAlgorithm(options.KeyAlgorithm)
|
||||||
|
|
||||||
// A client facilitates communication with the CA server.
|
// Create an ACME client
|
||||||
client, err := lego.NewClient(config)
|
client, err := lego.NewClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the DNS01 challenge provider
|
||||||
challengeOptions := make([]dns01.ChallengeOption, 0)
|
challengeOptions := make([]dns01.ChallengeOption, 0)
|
||||||
if len(options.Nameservers) > 0 {
|
if len(options.Nameservers) > 0 {
|
||||||
challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers)))
|
challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers)))
|
||||||
challengeOptions = append(challengeOptions, dns01.DisableAuthoritativeNssPropagationRequirement())
|
challengeOptions = append(challengeOptions, dns01.DisableAuthoritativeNssPropagationRequirement())
|
||||||
}
|
}
|
||||||
|
|
||||||
client.Challenge.SetDNS01Provider(challengeProvider, challengeOptions...)
|
client.Challenge.SetDNS01Provider(challengeProvider, challengeOptions...)
|
||||||
|
|
||||||
// New users will need to register
|
// New users need to register first
|
||||||
if !myUser.hasRegistration() {
|
if !acmeUser.hasRegistration() {
|
||||||
reg, err := registerAcmeUser(client, sslProviderConfig, myUser)
|
reg, err := registerAcmeUser(client, sslProviderConfig, acmeUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to register: %w", err)
|
return nil, fmt.Errorf("failed to register: %w", err)
|
||||||
}
|
}
|
||||||
myUser.Registration = reg
|
acmeUser.Registration = reg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Obtain a certificate
|
||||||
certRequest := certificate.ObtainRequest{
|
certRequest := certificate.ObtainRequest{
|
||||||
Domains: options.Domains,
|
Domains: options.Domains,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
}
|
||||||
|
if !options.DisableARI {
|
||||||
|
certRequest.ReplacesCertID = options.ReplacedARICertId
|
||||||
|
}
|
||||||
certResource, err := client.Certificate.Obtain(certRequest)
|
certResource, err := client.Certificate.Obtain(certRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -162,7 +185,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
|||||||
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
|
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
|
||||||
ACMECertUrl: certResource.CertURL,
|
ACMECertUrl: certResource.CertURL,
|
||||||
ACMECertStableUrl: certResource.CertStableURL,
|
ACMECertStableUrl: certResource.CertStableURL,
|
||||||
CSR: string(certResource.CSR),
|
CSR: strings.TrimSpace(string(certResource.CSR)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ type WorkflowNodeConfigForApply struct {
|
|||||||
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(默认取决于提供商)
|
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(默认取决于提供商)
|
||||||
DnsTTL int32 `json:"dnsTTL"` // DNS TTL(默认取决于提供商)
|
DnsTTL int32 `json:"dnsTTL"` // DNS TTL(默认取决于提供商)
|
||||||
DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否禁用 CNAME 跟随
|
DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否禁用 CNAME 跟随
|
||||||
|
DisableARI bool `json:"disableARI"` // 是否禁用 ARI
|
||||||
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(默认值:30)
|
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(默认值:30)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +125,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply {
|
|||||||
DnsPropagationTimeout: n.getConfigValueAsInt32("dnsPropagationTimeout"),
|
DnsPropagationTimeout: n.getConfigValueAsInt32("dnsPropagationTimeout"),
|
||||||
DnsTTL: n.getConfigValueAsInt32("dnsTTL"),
|
DnsTTL: n.getConfigValueAsInt32("dnsTTL"),
|
||||||
DisableFollowCNAME: n.getConfigValueAsBool("disableFollowCNAME"),
|
DisableFollowCNAME: n.getConfigValueAsBool("disableFollowCNAME"),
|
||||||
|
DisableARI: n.getConfigValueAsBool("disableARI"),
|
||||||
SkipBeforeExpiryDays: skipBeforeExpiryDays,
|
SkipBeforeExpiryDays: skipBeforeExpiryDays,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package nodeprocessor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -131,8 +132,9 @@ func (a *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo
|
|||||||
|
|
||||||
lastCertificate, _ := a.certRepo.GetByWorkflowNodeId(ctx, a.node.Id)
|
lastCertificate, _ := a.certRepo.GetByWorkflowNodeId(ctx, a.node.Id)
|
||||||
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
|
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
|
||||||
if lastCertificate != nil && time.Until(lastCertificate.ExpireAt) > renewalInterval {
|
expirationTime := time.Until(lastCertificate.ExpireAt)
|
||||||
return true, "已申请过证书,且证书尚未临近过期"
|
if lastCertificate != nil && expirationTime > renewalInterval {
|
||||||
|
return true, fmt.Sprintf("已申请过证书,且证书尚未临近过期(到期尚余 %d 天,距 %d 天时续期)", int(expirationTime.Hours()/24), currentNodeConfig.SkipBeforeExpiryDays)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,6 @@ const MULTIPLE_INPUT_DELIMITER = ";";
|
|||||||
const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
const initFormModel = (): ApplyNodeConfigFormFieldValues => {
|
||||||
return {
|
return {
|
||||||
keyAlgorithm: "RSA2048",
|
keyAlgorithm: "RSA2048",
|
||||||
disableFollowCNAME: true,
|
|
||||||
skipBeforeExpiryDays: 20,
|
skipBeforeExpiryDays: 20,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -105,11 +104,11 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
])
|
])
|
||||||
.nullish(),
|
.nullish(),
|
||||||
disableFollowCNAME: z.boolean().nullish(),
|
disableFollowCNAME: z.boolean().nullish(),
|
||||||
|
disableARI: z.boolean().nullish(),
|
||||||
skipBeforeExpiryDays: z
|
skipBeforeExpiryDays: z
|
||||||
.number({ message: t("workflow_node.apply.form.skip_before_expiry_days.placeholder") })
|
.number({ message: t("workflow_node.apply.form.skip_before_expiry_days.placeholder") })
|
||||||
.int(t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
.int(t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
||||||
.gte(1, t("workflow_node.apply.form.skip_before_expiry_days.placeholder"))
|
.gte(1, t("workflow_node.apply.form.skip_before_expiry_days.placeholder")),
|
||||||
.lte(60, t("workflow_node.apply.form.skip_before_expiry_days.placeholder")),
|
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
const { form: formInst, formProps } = useAntdForm({
|
const { form: formInst, formProps } = useAntdForm({
|
||||||
@ -378,6 +377,15 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="disableARI"
|
||||||
|
label={t("workflow_node.apply.form.disable_ari.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.disable_ari.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Divider className="my-1">
|
<Divider className="my-1">
|
||||||
@ -397,7 +405,7 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
|
|||||||
<InputNumber
|
<InputNumber
|
||||||
className="w-36"
|
className="w-36"
|
||||||
min={1}
|
min={1}
|
||||||
max={60}
|
max={90}
|
||||||
placeholder={t("workflow_node.apply.form.skip_before_expiry_days.placeholder")}
|
placeholder={t("workflow_node.apply.form.skip_before_expiry_days.placeholder")}
|
||||||
addonAfter={t("workflow_node.apply.form.skip_before_expiry_days.unit")}
|
addonAfter={t("workflow_node.apply.form.skip_before_expiry_days.unit")}
|
||||||
/>
|
/>
|
||||||
|
@ -92,11 +92,8 @@ const DeployNode = ({ node, disabled }: DeployNodeProps) => {
|
|||||||
pending={formPending}
|
pending={formPending}
|
||||||
onConfirm={handleDrawerConfirm}
|
onConfirm={handleDrawerConfirm}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
setDrawerFooterShow(!!(node.config as WorkflowNodeConfigForDeploy)?.provider);
|
||||||
setDrawerOpen(open);
|
setDrawerOpen(open);
|
||||||
|
|
||||||
if (!open) {
|
|
||||||
setDrawerFooterShow(!!(node.config as WorkflowNodeConfigForDeploy)?.provider);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
getFormValues={() => formRef.current!.getFieldsValue()}
|
getFormValues={() => formRef.current!.getFieldsValue()}
|
||||||
>
|
>
|
||||||
|
@ -342,28 +342,28 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
|||||||
</Divider>
|
</Divider>
|
||||||
|
|
||||||
{nestedFormEl}
|
{nestedFormEl}
|
||||||
|
|
||||||
|
<Divider className="my-1">
|
||||||
|
<Typography.Text className="text-xs font-normal" type="secondary">
|
||||||
|
{t("workflow_node.deploy.form.strategy_config.label")}
|
||||||
|
</Typography.Text>
|
||||||
|
</Divider>
|
||||||
|
|
||||||
|
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
||||||
|
<Form.Item label={t("workflow_node.deploy.form.skip_on_last_succeeded.label")}>
|
||||||
|
<Flex align="center" gap={8} wrap="wrap">
|
||||||
|
<div>{t("workflow_node.deploy.form.skip_on_last_succeeded.prefix")}</div>
|
||||||
|
<Form.Item name="skipOnLastSucceeded" noStyle rules={[formRule]}>
|
||||||
|
<Switch
|
||||||
|
checkedChildren={t("workflow_node.deploy.form.skip_on_last_succeeded.enabled.on")}
|
||||||
|
unCheckedChildren={t("workflow_node.deploy.form.skip_on_last_succeeded.enabled.off")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<div>{t("workflow_node.deploy.form.skip_on_last_succeeded.suffix")}</div>
|
||||||
|
</Flex>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Divider className="my-1">
|
|
||||||
<Typography.Text className="text-xs font-normal" type="secondary">
|
|
||||||
{t("workflow_node.deploy.form.strategy_config.label")}
|
|
||||||
</Typography.Text>
|
|
||||||
</Divider>
|
|
||||||
|
|
||||||
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
|
|
||||||
<Form.Item label={t("workflow_node.deploy.form.skip_on_last_succeeded.label")}>
|
|
||||||
<Flex align="center" gap={8} wrap="wrap">
|
|
||||||
<div>{t("workflow_node.deploy.form.skip_on_last_succeeded.prefix")}</div>
|
|
||||||
<Form.Item name="skipOnLastSucceeded" noStyle rules={[formRule]}>
|
|
||||||
<Switch
|
|
||||||
checkedChildren={t("workflow_node.deploy.form.skip_on_last_succeeded.enabled.on")}
|
|
||||||
unCheckedChildren={t("workflow_node.deploy.form.skip_on_last_succeeded.enabled.off")}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<div>{t("workflow_node.deploy.form.skip_on_last_succeeded.suffix")}</div>
|
|
||||||
</Flex>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Form.Provider>
|
</Form.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ export type WorkflowNodeConfigForApply = {
|
|||||||
dnsPropagationTimeout?: number;
|
dnsPropagationTimeout?: number;
|
||||||
dnsTTL?: number;
|
dnsTTL?: number;
|
||||||
disableFollowCNAME?: boolean;
|
disableFollowCNAME?: boolean;
|
||||||
|
disableARI?: boolean;
|
||||||
skipBeforeExpiryDays: number;
|
skipBeforeExpiryDays: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
"workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm",
|
"workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm",
|
||||||
"workflow_node.apply.form.nameservers.label": "DNS recursive nameservers (Optional)",
|
"workflow_node.apply.form.nameservers.label": "DNS recursive nameservers (Optional)",
|
||||||
"workflow_node.apply.form.nameservers.placeholder": "Please enter DNS recursive nameservers (separated by semicolons)",
|
"workflow_node.apply.form.nameservers.placeholder": "Please enter DNS recursive nameservers (separated by semicolons)",
|
||||||
"workflow_node.apply.form.nameservers.tooltip": "It determines whether to custom DNS recursive nameservers during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<a href=\"https://go-acme.github.io/lego/usage/cli/options/index.html#dns-resolvers-and-challenge-verification\" target=\"_blank\">Learn more</a>.",
|
"workflow_node.apply.form.nameservers.tooltip": "It determines whether to custom DNS recursive nameservers during ACME DNS-01 authentication. If you don't understand this option, just keep it by default. <a href=\"https://go-acme.github.io/lego/usage/cli/options/index.html#dns-resolvers-and-challenge-verification\" target=\"_blank\">Learn more</a>.",
|
||||||
"workflow_node.apply.form.nameservers.multiple_input_modal.title": "Change DNS rcursive nameservers",
|
"workflow_node.apply.form.nameservers.multiple_input_modal.title": "Change DNS rcursive nameservers",
|
||||||
"workflow_node.apply.form.nameservers.multiple_input_modal.placeholder": "Please enter DNS recursive nameserver",
|
"workflow_node.apply.form.nameservers.multiple_input_modal.placeholder": "Please enter DNS recursive nameserver",
|
||||||
"workflow_node.apply.form.dns_propagation_timeout.label": "DNS propagation timeout (Optional)",
|
"workflow_node.apply.form.dns_propagation_timeout.label": "DNS propagation timeout (Optional)",
|
||||||
@ -63,7 +63,9 @@
|
|||||||
"workflow_node.apply.form.dns_ttl.unit": "seconds",
|
"workflow_node.apply.form.dns_ttl.unit": "seconds",
|
||||||
"workflow_node.apply.form.dns_ttl.tooltip": "It determines the time to live for DNS record during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<br><br>Leave blank to use the default value provided by the provider.",
|
"workflow_node.apply.form.dns_ttl.tooltip": "It determines the time to live for DNS record during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<br><br>Leave blank to use the default value provided by the provider.",
|
||||||
"workflow_node.apply.form.disable_follow_cname.label": "Disable CNAME following",
|
"workflow_node.apply.form.disable_follow_cname.label": "Disable CNAME following",
|
||||||
"workflow_node.apply.form.disable_follow_cname.tooltip": "It determines whether to disable CNAME following during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.<a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">Learn more</a>.",
|
"workflow_node.apply.form.disable_follow_cname.tooltip": "It determines whether to disable CNAME following during ACME DNS-01 authentication. If you don't understand this option, just keep it by default. <a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">Learn more</a>.",
|
||||||
|
"workflow_node.apply.form.disable_ari.label": "Disable ARI",
|
||||||
|
"workflow_node.apply.form.disable_ari.tooltip": "It determines whether to disable ARI (ACME Renewal Information). If you don't understand this option, just keep it by default. <a href=\"https://letsencrypt.org/2023/03/23/improving-resliiency-and-reliability-with-ari/\" target=\"_blank\">Learn more</a>.",
|
||||||
"workflow_node.apply.form.strategy_config.label": "Strategy settings",
|
"workflow_node.apply.form.strategy_config.label": "Strategy settings",
|
||||||
"workflow_node.apply.form.skip_before_expiry_days.label": "Renewal interval",
|
"workflow_node.apply.form.skip_before_expiry_days.label": "Renewal interval",
|
||||||
"workflow_node.apply.form.skip_before_expiry_days.placeholder": "Please enter renewal interval",
|
"workflow_node.apply.form.skip_before_expiry_days.placeholder": "Please enter renewal interval",
|
||||||
|
@ -62,12 +62,14 @@
|
|||||||
"workflow_node.apply.form.dns_ttl.placeholder": "请输入 DNS 解析 TTL",
|
"workflow_node.apply.form.dns_ttl.placeholder": "请输入 DNS 解析 TTL",
|
||||||
"workflow_node.apply.form.dns_ttl.unit": "秒",
|
"workflow_node.apply.form.dns_ttl.unit": "秒",
|
||||||
"workflow_node.apply.form.dns_ttl.tooltip": "在 ACME DNS-01 认证时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。<br><br>为空时,将使用提供商提供的默认值。",
|
"workflow_node.apply.form.dns_ttl.tooltip": "在 ACME DNS-01 认证时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。<br><br>为空时,将使用提供商提供的默认值。",
|
||||||
"workflow_node.apply.form.disable_follow_cname.label": "禁止 CNAME 跟随",
|
"workflow_node.apply.form.disable_follow_cname.label": "关闭 CNAME 跟随",
|
||||||
"workflow_node.apply.form.disable_follow_cname.tooltip": "在 ACME DNS-01 认证时是否禁止 CNAME 跟随。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">点此了解更多</a>。",
|
"workflow_node.apply.form.disable_follow_cname.tooltip": "在 ACME DNS-01 认证时是否关闭 CNAME 跟随。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname\" target=\"_blank\">点此了解更多</a>。",
|
||||||
|
"workflow_node.apply.form.disable_ari.label": "关闭 ARI 续期",
|
||||||
|
"workflow_node.apply.form.disable_ari.tooltip": "在 ACME 证书续期时是否关闭 ARI(ACME Renewal Information)。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/2023/03/23/improving-resliiency-and-reliability-with-ari/\" target=\"_blank\">点此了解更多</a>。",
|
||||||
"workflow_node.apply.form.strategy_config.label": "执行策略",
|
"workflow_node.apply.form.strategy_config.label": "执行策略",
|
||||||
"workflow_node.apply.form.skip_before_expiry_days.label": "续期间隔",
|
"workflow_node.apply.form.skip_before_expiry_days.label": "续期间隔",
|
||||||
"workflow_node.apply.form.skip_before_expiry_days.placeholder": "请输入续期间隔",
|
"workflow_node.apply.form.skip_before_expiry_days.placeholder": "请输入续期间隔",
|
||||||
"workflow_node.apply.form.skip_before_expiry_days.prefix": "当上次签发的证书到期时间超过",
|
"workflow_node.apply.form.skip_before_expiry_days.prefix": "当上次签发的证书距到期时间超过",
|
||||||
"workflow_node.apply.form.skip_before_expiry_days.suffix": "时,跳过重新申请。",
|
"workflow_node.apply.form.skip_before_expiry_days.suffix": "时,跳过重新申请。",
|
||||||
"workflow_node.apply.form.skip_before_expiry_days.unit": "天",
|
"workflow_node.apply.form.skip_before_expiry_days.unit": "天",
|
||||||
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过 CA 的证书有效期限制,否则证书可能永远不会续期。",
|
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过 CA 的证书有效期限制,否则证书可能永远不会续期。",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user