feat: migrate to urfave/cli v3 (#2806)

This commit is contained in:
Ludovic Fernandez
2026-01-21 17:38:22 +01:00
committed by GitHub
parent 8013d0fba4
commit c24105e0c8
18 changed files with 407 additions and 405 deletions

View File

@@ -1,6 +1,7 @@
package cmd
import (
"context"
"crypto"
"encoding/json"
"encoding/pem"
@@ -13,7 +14,7 @@ import (
"github.com/go-acme/lego/v5/lego"
"github.com/go-acme/lego/v5/log"
"github.com/go-acme/lego/v5/registration"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const userIDPlaceholder = "noemail@example.com"
@@ -64,25 +65,25 @@ type AccountsStorage struct {
rootUserPath string
keysPath string
accountFilePath string
ctx *cli.Context
cmd *cli.Command
}
// NewAccountsStorage Creates a new AccountsStorage.
func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
func NewAccountsStorage(cmd *cli.Command) *AccountsStorage {
// TODO: move to account struct?
email := ctx.String(flgEmail)
email := cmd.String(flgEmail)
userID := email
if userID == "" {
userID = userIDPlaceholder
}
serverURL, err := url.Parse(ctx.String(flgServer))
serverURL, err := url.Parse(cmd.String(flgServer))
if err != nil {
log.Fatal("URL parsing", "flag", flgServer, "serverURL", ctx.String(flgServer), "error", err)
log.Fatal("URL parsing", "flag", flgServer, "serverURL", cmd.String(flgServer), "error", err)
}
rootPath := filepath.Join(ctx.String(flgPath), baseAccountsRootFolderName)
rootPath := filepath.Join(cmd.String(flgPath), baseAccountsRootFolderName)
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
accountsPath := filepath.Join(rootPath, serverPath)
rootUserPath := filepath.Join(accountsPath, userID)
@@ -94,7 +95,7 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
rootUserPath: rootUserPath,
keysPath: filepath.Join(rootUserPath, baseKeysFolderName),
accountFilePath: filepath.Join(rootUserPath, accountFileName),
ctx: ctx,
cmd: cmd,
}
}
@@ -134,7 +135,7 @@ func (s *AccountsStorage) Save(account *Account) error {
return os.WriteFile(s.accountFilePath, jsonBytes, filePerm)
}
func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
func (s *AccountsStorage) LoadAccount(ctx context.Context, privateKey crypto.PrivateKey) *Account {
fileBytes, err := os.ReadFile(s.accountFilePath)
if err != nil {
log.Fatal("Could not load the account file.", "userID", s.GetUserID(), "error", err)
@@ -150,7 +151,7 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
account.key = privateKey
if account.Registration == nil || account.Registration.Body.Status == "" {
reg, err := tryRecoverRegistration(s.ctx, privateKey)
reg, err := tryRecoverRegistration(ctx, s.cmd, privateKey)
if err != nil {
log.Fatal("Could not load the account file. Registration is nil.", "userID", s.GetUserID(), "error", err)
}
@@ -233,18 +234,18 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
return privateKey, nil
}
func tryRecoverRegistration(cliCtx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) {
func tryRecoverRegistration(ctx context.Context, cmd *cli.Command, privateKey crypto.PrivateKey) (*registration.Resource, error) {
// couldn't load account but got a key. Try to look the account up.
config := lego.NewConfig(&Account{key: privateKey})
config.CADirURL = cliCtx.String(flgServer)
config.UserAgent = getUserAgent(cliCtx)
config.CADirURL = cmd.String(flgServer)
config.UserAgent = getUserAgent(cmd)
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
reg, err := client.Registration.ResolveAccountByKey(cliCtx.Context)
reg, err := client.Registration.ResolveAccountByKey(ctx)
if err != nil {
return nil, err
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/go-acme/lego/v5/certcrypto"
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"golang.org/x/net/idna"
"software.sslmate.com/src/go-pkcs12"
)
@@ -59,8 +59,8 @@ type CertificatesStorage struct {
}
// NewCertificatesStorage create a new certificates storage.
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
pfxFormat := ctx.String(flgPFXFormat)
func NewCertificatesStorage(cmd *cli.Command) *CertificatesStorage {
pfxFormat := cmd.String(flgPFXFormat)
switch pfxFormat {
case "DES", "RC2", "SHA256":
@@ -69,13 +69,13 @@ func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
}
return &CertificatesStorage{
rootPath: filepath.Join(ctx.String(flgPath), baseCertificatesFolderName),
archivePath: filepath.Join(ctx.String(flgPath), baseArchivesFolderName),
pem: ctx.Bool(flgPEM),
pfx: ctx.Bool(flgPFX),
pfxPassword: ctx.String(flgPFXPass),
rootPath: filepath.Join(cmd.String(flgPath), baseCertificatesFolderName),
archivePath: filepath.Join(cmd.String(flgPath), baseArchivesFolderName),
pem: cmd.Bool(flgPEM),
pfx: cmd.Bool(flgPFX),
pfxPassword: cmd.String(flgPFXPass),
pfxFormat: pfxFormat,
filename: ctx.String(flgFilename),
filename: cmd.String(flgFilename),
}
}

View File

@@ -1,6 +1,6 @@
package cmd
import "github.com/urfave/cli/v2"
import "github.com/urfave/cli/v3"
// CreateCommands Creates all CLI commands.
func CreateCommands() []*cli.Command {

View File

@@ -1,25 +1,26 @@
package cmd
import (
"context"
"fmt"
"github.com/go-acme/lego/v5/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func Before(ctx *cli.Context) error {
if ctx.String(flgPath) == "" {
func Before(ctx context.Context, cmd *cli.Command) (context.Context, error) {
if cmd.String(flgPath) == "" {
log.Fatal(fmt.Sprintf("Could not determine the current working directory. Please pass --%s.", flgPath))
}
err := createNonExistingFolder(ctx.String(flgPath))
err := createNonExistingFolder(cmd.String(flgPath))
if err != nil {
log.Fatal("Could not check/create the path.", "flag", flgPath, "filepath", ctx.String(flgPath), "error", err)
log.Fatal("Could not check/create the path.", "flag", flgPath, "filepath", cmd.String(flgPath), "error", err)
}
if ctx.String(flgServer) == "" {
if cmd.String(flgServer) == "" {
log.Fatal(fmt.Sprintf("Could not determine the current working server. Please pass --%s.", flgServer))
}
return nil
return ctx, nil
}

View File

@@ -1,12 +1,13 @@
package cmd
import (
"context"
"fmt"
"io"
"strings"
"text/tabwriter"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const flgCode = "code"
@@ -26,10 +27,10 @@ func createDNSHelp() *cli.Command {
}
}
func dnsHelp(ctx *cli.Context) error {
code := ctx.String(flgCode)
func dnsHelp(_ context.Context, cmd *cli.Command) error {
code := cmd.String(flgCode)
if code == "" {
w := tabwriter.NewWriter(ctx.App.Writer, 0, 0, 2, ' ', 0)
w := tabwriter.NewWriter(cmd.Writer, 0, 0, 2, ' ', 0)
ew := &errWriter{w: w}
ew.writeln(`Credentials for DNS providers must be passed through environment variables.`)
@@ -50,7 +51,7 @@ func dnsHelp(ctx *cli.Context) error {
return w.Flush()
}
return displayDNSHelp(ctx.App.Writer, strings.ToLower(code))
return displayDNSHelp(cmd.Writer, strings.ToLower(code))
}
type errWriter struct {

View File

@@ -1,6 +1,7 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"net/url"
@@ -9,7 +10,7 @@ import (
"strings"
"github.com/go-acme/lego/v5/certcrypto"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@@ -43,25 +44,25 @@ func createList() *cli.Command {
}
}
func list(ctx *cli.Context) error {
if ctx.Bool(flgAccounts) && !ctx.Bool(flgNames) {
if err := listAccount(ctx); err != nil {
func list(ctx context.Context, cmd *cli.Command) error {
if cmd.Bool(flgAccounts) && !cmd.Bool(flgNames) {
if err := listAccount(ctx, cmd); err != nil {
return err
}
}
return listCertificates(ctx)
return listCertificates(ctx, cmd)
}
func listCertificates(ctx *cli.Context) error {
certsStorage := NewCertificatesStorage(ctx)
func listCertificates(_ context.Context, cmd *cli.Command) error {
certsStorage := NewCertificatesStorage(cmd)
matches, err := filepath.Glob(filepath.Join(certsStorage.GetRootPath(), "*.crt"))
if err != nil {
return err
}
names := ctx.Bool(flgNames)
names := cmd.Bool(flgNames)
if len(matches) == 0 {
if !names {
@@ -109,8 +110,8 @@ func listCertificates(ctx *cli.Context) error {
return nil
}
func listAccount(ctx *cli.Context) error {
accountsStorage := NewAccountsStorage(ctx)
func listAccount(_ context.Context, cmd *cli.Command) error {
accountsStorage := NewAccountsStorage(cmd)
matches, err := filepath.Glob(filepath.Join(accountsStorage.GetRootPath(), "*", "*", "*.json"))
if err != nil {
@@ -118,7 +119,7 @@ func listAccount(ctx *cli.Context) error {
}
if len(matches) == 0 {
fmt.Println("No accounts found.")
fmt.Println("No accounts were found.")
return nil
}

View File

@@ -1,6 +1,7 @@
package cmd
import (
"context"
"crypto"
"crypto/x509"
"errors"
@@ -16,7 +17,7 @@ import (
"github.com/go-acme/lego/v5/lego"
"github.com/go-acme/lego/v5/log"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Flag names.
@@ -37,11 +38,11 @@ func createRenew() *cli.Command {
Name: "renew",
Usage: "Renew a certificate",
Action: renew,
Before: func(ctx *cli.Context) error {
Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
// we require either domains or csr, but not both
hasDomains := len(ctx.StringSlice(flgDomains)) > 0
hasDomains := len(cmd.StringSlice(flgDomains)) > 0
hasCsr := ctx.String(flgCSR) != ""
hasCsr := cmd.String(flgCSR) != ""
if hasDomains && hasCsr {
log.Fatal(fmt.Sprintf("Please specify either --%s/-d or --%s/-c, but not both", flgDomains, flgCSR))
}
@@ -50,11 +51,11 @@ func createRenew() *cli.Command {
log.Fatal(fmt.Sprintf("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR))
}
if ctx.Bool(flgForceCertDomains) && hasCsr {
if cmd.Bool(flgForceCertDomains) && hasCsr {
log.Fatal(fmt.Sprintf("--%s only works with --%s/-d, --%s/-c doesn't support this option.", flgForceCertDomains, flgDomains, flgCSR))
}
return nil
return ctx, nil
},
Flags: []cli.Flag{
&cli.IntFlag{
@@ -90,14 +91,18 @@ func createRenew() *cli.Command {
" Only works if the CSR is generated by lego.",
},
&cli.TimestampFlag{
Name: flgNotBefore,
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Layout: time.RFC3339,
Name: flgNotBefore,
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.TimestampFlag{
Name: flgNotAfter,
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Layout: time.RFC3339,
Name: flgNotAfter,
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.StringFlag{
Name: flgPreferredChain,
@@ -134,32 +139,32 @@ func createRenew() *cli.Command {
}
}
func renew(cliCtx *cli.Context) error {
account, keyType := setupAccount(cliCtx, NewAccountsStorage(cliCtx))
func renew(ctx context.Context, cmd *cli.Command) error {
account, keyType := setupAccount(ctx, cmd, NewAccountsStorage(cmd))
if account.Registration == nil {
log.Fatal("The account is not registered. Use 'run' to register a new account.", "email", account.Email)
}
certsStorage := NewCertificatesStorage(cliCtx)
certsStorage := NewCertificatesStorage(cmd)
bundle := !cliCtx.Bool(flgNoBundle)
bundle := !cmd.Bool(flgNoBundle)
meta := map[string]string{
hookEnvAccountEmail: account.Email,
}
// CSR
if cliCtx.IsSet(flgCSR) {
return renewForCSR(cliCtx, account, keyType, certsStorage, bundle, meta)
if cmd.IsSet(flgCSR) {
return renewForCSR(ctx, cmd, account, keyType, certsStorage, bundle, meta)
}
// Domains
return renewForDomains(cliCtx, account, keyType, certsStorage, bundle, meta)
return renewForDomains(ctx, cmd, account, keyType, certsStorage, bundle, meta)
}
func renewForDomains(cliCtx *cli.Context, account *Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
domains := cliCtx.StringSlice(flgDomains)
func renewForDomains(ctx context.Context, cmd *cli.Command, account *Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
domains := cmd.StringSlice(flgDomains)
domain := domains[0]
// load the cert resource from files.
@@ -179,10 +184,10 @@ func renewForDomains(cliCtx *cli.Context, account *Account, keyType certcrypto.K
var client *lego.Client
if !cliCtx.Bool(flgARIDisable) {
client = setupClient(cliCtx, account, keyType)
if !cmd.Bool(flgARIDisable) {
client = setupClient(cmd, account, keyType)
ariRenewalTime = getARIRenewalTime(cliCtx, cert, domain, client)
ariRenewalTime = getARIRenewalTime(ctx, cmd, cert, domain, client)
if ariRenewalTime != nil {
now := time.Now().UTC()
@@ -199,17 +204,17 @@ func renewForDomains(cliCtx *cli.Context, account *Account, keyType certcrypto.K
}
}
forceDomains := cliCtx.Bool(flgForceCertDomains)
forceDomains := cmd.Bool(flgForceCertDomains)
certDomains := certcrypto.ExtractDomains(cert)
if ariRenewalTime == nil && !needRenewal(cert, domain, cliCtx.Int(flgRenewDays), cliCtx.Bool(flgRenewDynamic)) &&
if ariRenewalTime == nil && !needRenewal(cert, domain, cmd.Int(flgRenewDays), cmd.Bool(flgRenewDynamic)) &&
(!forceDomains || slices.Equal(certDomains, domains)) {
return nil
}
if client == nil {
client = setupClient(cliCtx, account, keyType)
client = setupClient(cmd, account, keyType)
}
// This is just meant to be informal for the user.
@@ -218,7 +223,7 @@ func renewForDomains(cliCtx *cli.Context, account *Account, keyType certcrypto.K
var privateKey crypto.PrivateKey
if cliCtx.Bool(flgReuseKey) {
if cmd.Bool(flgReuseKey) {
keyBytes, errR := certsStorage.ReadFile(domain, keyExt)
if errR != nil {
log.Fatal("Error while loading the private key.", "domain", domain, "error", errR)
@@ -232,7 +237,7 @@ func renewForDomains(cliCtx *cli.Context, account *Account, keyType certcrypto.K
// https://github.com/go-acme/lego/issues/1656
// https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L435-L440
if !isatty.IsTerminal(os.Stdout.Fd()) && !cliCtx.Bool(flgNoRandomSleep) {
if !isatty.IsTerminal(os.Stdout.Fd()) && !cmd.Bool(flgNoRandomSleep) {
// https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L472
const jitter = 8 * time.Minute
@@ -251,20 +256,20 @@ func renewForDomains(cliCtx *cli.Context, account *Account, keyType certcrypto.K
request := certificate.ObtainRequest{
Domains: renewalDomains,
PrivateKey: privateKey,
MustStaple: cliCtx.Bool(flgMustStaple),
NotBefore: getTime(cliCtx, flgNotBefore),
NotAfter: getTime(cliCtx, flgNotAfter),
MustStaple: cmd.Bool(flgMustStaple),
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: bundle,
PreferredChain: cliCtx.String(flgPreferredChain),
Profile: cliCtx.String(flgProfile),
AlwaysDeactivateAuthorizations: cliCtx.Bool(flgAlwaysDeactivateAuthorizations),
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
if replacesCertID != "" {
request.ReplacesCertID = replacesCertID
}
certRes, err := client.Certificate.Obtain(cliCtx.Context, request)
certRes, err := client.Certificate.Obtain(ctx, request)
if err != nil {
log.Fatal("Could not obtain the certificate.", "error", err)
}
@@ -275,13 +280,13 @@ func renewForDomains(cliCtx *cli.Context, account *Account, keyType certcrypto.K
addPathToMetadata(meta, domain, certRes, certsStorage)
return launchHook(cliCtx.Context, cliCtx.String(flgRenewHook), cliCtx.Duration(flgRenewHookTimeout), meta)
return launchHook(ctx, cmd.String(flgRenewHook), cmd.Duration(flgRenewHookTimeout), meta)
}
func renewForCSR(cliCtx *cli.Context, account *Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
csr, err := readCSRFile(cliCtx.String(flgCSR))
func renewForCSR(ctx context.Context, cmd *cli.Command, account *Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
csr, err := readCSRFile(cmd.String(flgCSR))
if err != nil {
log.Fatal("Could not read CSR file.", "flag", flgCSR, "filepath", cliCtx.String(flgCSR), "error", err)
log.Fatal("Could not read CSR file.", "flag", flgCSR, "filepath", cmd.String(flgCSR), "error", err)
}
domain, err := certcrypto.GetCSRMainDomain(csr)
@@ -306,10 +311,10 @@ func renewForCSR(cliCtx *cli.Context, account *Account, keyType certcrypto.KeyTy
var client *lego.Client
if !cliCtx.Bool(flgARIDisable) {
client = setupClient(cliCtx, account, keyType)
if !cmd.Bool(flgARIDisable) {
client = setupClient(cmd, account, keyType)
ariRenewalTime = getARIRenewalTime(cliCtx, cert, domain, client)
ariRenewalTime = getARIRenewalTime(ctx, cmd, cert, domain, client)
if ariRenewalTime != nil {
now := time.Now().UTC()
@@ -326,12 +331,12 @@ func renewForCSR(cliCtx *cli.Context, account *Account, keyType certcrypto.KeyTy
}
}
if ariRenewalTime == nil && !needRenewal(cert, domain, cliCtx.Int(flgRenewDays), cliCtx.Bool(flgRenewDynamic)) {
if ariRenewalTime == nil && !needRenewal(cert, domain, cmd.Int(flgRenewDays), cmd.Bool(flgRenewDynamic)) {
return nil
}
if client == nil {
client = setupClient(cliCtx, account, keyType)
client = setupClient(cmd, account, keyType)
}
// This is just meant to be informal for the user.
@@ -340,19 +345,19 @@ func renewForCSR(cliCtx *cli.Context, account *Account, keyType certcrypto.KeyTy
request := certificate.ObtainForCSRRequest{
CSR: csr,
NotBefore: getTime(cliCtx, flgNotBefore),
NotAfter: getTime(cliCtx, flgNotAfter),
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: bundle,
PreferredChain: cliCtx.String(flgPreferredChain),
Profile: cliCtx.String(flgProfile),
AlwaysDeactivateAuthorizations: cliCtx.Bool(flgAlwaysDeactivateAuthorizations),
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
if replacesCertID != "" {
request.ReplacesCertID = replacesCertID
}
certRes, err := client.Certificate.ObtainForCSR(cliCtx.Context, request)
certRes, err := client.Certificate.ObtainForCSR(ctx, request)
if err != nil {
log.Fatal("Could not obtain the certificate fro CSR.", "error", err)
}
@@ -361,7 +366,7 @@ func renewForCSR(cliCtx *cli.Context, account *Account, keyType certcrypto.KeyTy
addPathToMetadata(meta, domain, certRes, certsStorage)
return launchHook(cliCtx.Context, cliCtx.String(flgRenewHook), cliCtx.Duration(flgRenewHookTimeout), meta)
return launchHook(ctx, cmd.String(flgRenewHook), cmd.Duration(flgRenewHookTimeout), meta)
}
func needRenewal(x509Cert *x509.Certificate, domain string, days int, dynamic bool) bool {
@@ -409,12 +414,12 @@ func needRenewalDynamic(x509Cert *x509.Certificate, domain string, now time.Time
}
// getARIRenewalTime checks if the certificate needs to be renewed using the renewalInfo endpoint.
func getARIRenewalTime(cliCtx *cli.Context, cert *x509.Certificate, domain string, client *lego.Client) *time.Time {
func getARIRenewalTime(ctx context.Context, cmd *cli.Command, cert *x509.Certificate, domain string, client *lego.Client) *time.Time {
if cert.IsCA {
log.Fatal("Certificate bundle starts with a CA certificate.", "domain", domain)
}
renewalInfo, err := client.Certificate.GetRenewalInfo(cliCtx.Context, certificate.RenewalInfoRequest{Cert: cert})
renewalInfo, err := client.Certificate.GetRenewalInfo(ctx, certificate.RenewalInfoRequest{Cert: cert})
if err != nil {
if errors.Is(err, api.ErrNoARI) {
log.Warn("acme: the server does not advertise a renewal info endpoint.", "domain", domain, "errorr", err)
@@ -428,7 +433,7 @@ func getARIRenewalTime(cliCtx *cli.Context, cert *x509.Certificate, domain strin
now := time.Now().UTC()
renewalTime := renewalInfo.ShouldRenewAt(now, cliCtx.Duration(flgARIWaitToRenewDuration))
renewalTime := renewalInfo.ShouldRenewAt(now, cmd.Duration(flgARIWaitToRenewDuration))
if renewalTime == nil {
log.Info("acme: renewalInfo endpoint indicates that renewal is not needed.", "domain", domain)
return nil

View File

@@ -1,9 +1,11 @@
package cmd
import (
"context"
"github.com/go-acme/lego/v5/acme"
"github.com/go-acme/lego/v5/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Flag names.
@@ -37,19 +39,19 @@ func createRevoke() *cli.Command {
}
}
func revoke(cliCtx *cli.Context) error {
account, keyType := setupAccount(cliCtx, NewAccountsStorage(cliCtx))
func revoke(ctx context.Context, cmd *cli.Command) error {
account, keyType := setupAccount(ctx, cmd, NewAccountsStorage(cmd))
if account.Registration == nil {
log.Fatal("Account is not registered. Use 'run' to register a new account.", "email", account.Email)
}
client := newClient(cliCtx, account, keyType)
client := newClient(cmd, account, keyType)
certsStorage := NewCertificatesStorage(cliCtx)
certsStorage := NewCertificatesStorage(cmd)
certsStorage.CreateRootFolder()
for _, domain := range cliCtx.StringSlice(flgDomains) {
for _, domain := range cmd.StringSlice(flgDomains) {
log.Info("Trying to revoke the certificate.", "domain", domain)
certBytes, err := certsStorage.ReadFile(domain, certExt)
@@ -57,16 +59,16 @@ func revoke(cliCtx *cli.Context) error {
log.Fatal("Error while revoking the certificate.", "domain", domain, "error", err)
}
reason := cliCtx.Uint(flgReason)
reason := cmd.Uint(flgReason)
err = client.Certificate.RevokeWithReason(cliCtx.Context, certBytes, &reason)
err = client.Certificate.RevokeWithReason(ctx, certBytes, &reason)
if err != nil {
log.Fatal("Error while revoking the certificate.", "domain", domain, "error", err)
}
log.Info("Certificate was revoked.", "domain", domain)
if cliCtx.Bool(flgKeep) {
if cmd.Bool(flgKeep) {
return nil
}

View File

@@ -2,6 +2,7 @@ package cmd
import (
"bufio"
"context"
"fmt"
"os"
"strings"
@@ -11,7 +12,7 @@ import (
"github.com/go-acme/lego/v5/lego"
"github.com/go-acme/lego/v5/log"
"github.com/go-acme/lego/v5/registration"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Flag names.
@@ -32,11 +33,11 @@ func createRun() *cli.Command {
return &cli.Command{
Name: "run",
Usage: "Register an account, then create and install a certificate",
Before: func(ctx *cli.Context) error {
Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
// we require either domains or csr, but not both
hasDomains := len(ctx.StringSlice(flgDomains)) > 0
hasDomains := len(cmd.StringSlice(flgDomains)) > 0
hasCsr := ctx.String(flgCSR) != ""
hasCsr := cmd.String(flgCSR) != ""
if hasDomains && hasCsr {
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
}
@@ -45,7 +46,7 @@ func createRun() *cli.Command {
log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
}
return nil
return ctx, nil
},
Action: run,
Flags: []cli.Flag{
@@ -59,14 +60,18 @@ func createRun() *cli.Command {
" Only works if the CSR is generated by lego.",
},
&cli.TimestampFlag{
Name: flgNotBefore,
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Layout: time.RFC3339,
Name: flgNotBefore,
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.TimestampFlag{
Name: flgNotAfter,
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Layout: time.RFC3339,
Name: flgNotAfter,
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.StringFlag{
Name: flgPrivateKey,
@@ -109,15 +114,15 @@ generated by lego and certificates obtained from the ACME
server. Making regular backups of this folder is ideal.
`
func run(cliCtx *cli.Context) error {
accountsStorage := NewAccountsStorage(cliCtx)
func run(ctx context.Context, cmd *cli.Command) error {
accountsStorage := NewAccountsStorage(cmd)
account, keyType := setupAccount(cliCtx, accountsStorage)
account, keyType := setupAccount(ctx, cmd, accountsStorage)
client := setupClient(cliCtx, account, keyType)
client := setupClient(cmd, account, keyType)
if account.Registration == nil {
reg, err := register(cliCtx, client)
reg, err := register(ctx, cmd, client)
if err != nil {
log.Fatal("Could not complete registration.", "error", err)
}
@@ -130,10 +135,10 @@ func run(cliCtx *cli.Context) error {
fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath())
}
certsStorage := NewCertificatesStorage(cliCtx)
certsStorage := NewCertificatesStorage(cmd)
certsStorage.CreateRootFolder()
cert, err := obtainCertificate(cliCtx, client)
cert, err := obtainCertificate(ctx, cmd, client)
if err != nil {
// Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error.
// Due to us not returning partial certificate we can just exit here instead of at the end.
@@ -148,12 +153,12 @@ func run(cliCtx *cli.Context) error {
addPathToMetadata(meta, cert.Domain, cert, certsStorage)
return launchHook(cliCtx.Context, cliCtx.String(flgRunHook), cliCtx.Duration(flgRunHookTimeout), meta)
return launchHook(ctx, cmd.String(flgRunHook), cmd.Duration(flgRunHookTimeout), meta)
}
func handleTOS(ctx *cli.Context, client *lego.Client) bool {
func handleTOS(cmd *cli.Command, client *lego.Client) bool {
// Check for a global accept override
if ctx.Bool(flgAcceptTOS) {
if cmd.Bool(flgAcceptTOS) {
return true
}
@@ -181,61 +186,61 @@ func handleTOS(ctx *cli.Context, client *lego.Client) bool {
}
}
func register(cliCtx *cli.Context, client *lego.Client) (*registration.Resource, error) {
accepted := handleTOS(cliCtx, client)
func register(ctx context.Context, cmd *cli.Command, client *lego.Client) (*registration.Resource, error) {
accepted := handleTOS(cmd, client)
if !accepted {
log.Fatal("You did not accept the TOS. Unable to proceed.")
}
if cliCtx.Bool(flgEAB) {
kid := cliCtx.String(flgKID)
hmacEncoded := cliCtx.String(flgHMAC)
if cmd.Bool(flgEAB) {
kid := cmd.String(flgKID)
hmacEncoded := cmd.String(flgHMAC)
if kid == "" || hmacEncoded == "" {
log.Fatal(fmt.Sprintf("Requires arguments --%s and --%s.", flgKID, flgHMAC))
}
return client.Registration.RegisterWithExternalAccountBinding(cliCtx.Context, registration.RegisterEABOptions{
return client.Registration.RegisterWithExternalAccountBinding(ctx, registration.RegisterEABOptions{
TermsOfServiceAgreed: accepted,
Kid: kid,
HmacEncoded: hmacEncoded,
})
}
return client.Registration.Register(cliCtx.Context, registration.RegisterOptions{TermsOfServiceAgreed: true})
return client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true})
}
func obtainCertificate(cliCtx *cli.Context, client *lego.Client) (*certificate.Resource, error) {
bundle := !cliCtx.Bool(flgNoBundle)
func obtainCertificate(ctx context.Context, cmd *cli.Command, client *lego.Client) (*certificate.Resource, error) {
bundle := !cmd.Bool(flgNoBundle)
domains := cliCtx.StringSlice(flgDomains)
domains := cmd.StringSlice(flgDomains)
if len(domains) > 0 {
// obtain a certificate, generating a new private key
request := certificate.ObtainRequest{
Domains: domains,
MustStaple: cliCtx.Bool(flgMustStaple),
NotBefore: getTime(cliCtx, flgNotBefore),
NotAfter: getTime(cliCtx, flgNotAfter),
MustStaple: cmd.Bool(flgMustStaple),
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: bundle,
PreferredChain: cliCtx.String(flgPreferredChain),
Profile: cliCtx.String(flgProfile),
AlwaysDeactivateAuthorizations: cliCtx.Bool(flgAlwaysDeactivateAuthorizations),
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
if cliCtx.IsSet(flgPrivateKey) {
if cmd.IsSet(flgPrivateKey) {
var err error
request.PrivateKey, err = loadPrivateKey(cliCtx.String(flgPrivateKey))
request.PrivateKey, err = loadPrivateKey(cmd.String(flgPrivateKey))
if err != nil {
return nil, fmt.Errorf("load private key: %w", err)
}
}
return client.Certificate.Obtain(cliCtx.Context, request)
return client.Certificate.Obtain(ctx, request)
}
// read the CSR
csr, err := readCSRFile(cliCtx.String(flgCSR))
csr, err := readCSRFile(cmd.String(flgCSR))
if err != nil {
return nil, err
}
@@ -243,22 +248,22 @@ func obtainCertificate(cliCtx *cli.Context, client *lego.Client) (*certificate.R
// obtain a certificate for this CSR
request := certificate.ObtainForCSRRequest{
CSR: csr,
NotBefore: getTime(cliCtx, flgNotBefore),
NotAfter: getTime(cliCtx, flgNotAfter),
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: bundle,
PreferredChain: cliCtx.String(flgPreferredChain),
Profile: cliCtx.String(flgProfile),
AlwaysDeactivateAuthorizations: cliCtx.Bool(flgAlwaysDeactivateAuthorizations),
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
if cliCtx.IsSet(flgPrivateKey) {
if cmd.IsSet(flgPrivateKey) {
var err error
request.PrivateKey, err = loadPrivateKey(cliCtx.String(flgPrivateKey))
request.PrivateKey, err = loadPrivateKey(cmd.String(flgPrivateKey))
if err != nil {
return nil, fmt.Errorf("load private key: %w", err)
}
}
return client.Certificate.ObtainForCSR(cliCtx.Context, request)
return client.Certificate.ObtainForCSR(ctx, request)
}

View File

@@ -2,11 +2,12 @@ package cmd
import (
"fmt"
"time"
"os"
"path/filepath"
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/lego"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"software.sslmate.com/src/go-pkcs12"
)
@@ -65,6 +66,13 @@ const (
)
func CreateFlags(defaultPath string) []cli.Flag {
if defaultPath == "" {
cwd, err := os.Getwd()
if err == nil {
defaultPath = filepath.Join(cwd, ".lego")
}
}
return []cli.Flag{
&cli.StringSliceFlag{
Name: flgDomains,
@@ -74,7 +82,7 @@ func CreateFlags(defaultPath string) []cli.Flag {
&cli.StringFlag{
Name: flgServer,
Aliases: []string{"s"},
EnvVars: []string{envServer},
Sources: cli.EnvVars(envServer),
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
Value: lego.LEDirectoryProduction,
},
@@ -86,7 +94,7 @@ func CreateFlags(defaultPath string) []cli.Flag {
&cli.StringFlag{
Name: flgEmail,
Aliases: []string{"m"},
EnvVars: []string{envEmail},
Sources: cli.EnvVars(envEmail),
Usage: "Email used for registration and recovery contact.",
},
&cli.BoolFlag{
@@ -100,17 +108,17 @@ func CreateFlags(defaultPath string) []cli.Flag {
},
&cli.BoolFlag{
Name: flgEAB,
EnvVars: []string{envEAB},
Sources: cli.EnvVars(envEAB),
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
},
&cli.StringFlag{
Name: flgKID,
EnvVars: []string{envEABKID},
Sources: cli.EnvVars(envEABKID),
Usage: "Key identifier from External CA. Used for External Account Binding.",
},
&cli.StringFlag{
Name: flgHMAC,
EnvVars: []string{envEABHMAC},
Sources: cli.EnvVars(envEABHMAC),
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
},
&cli.StringFlag{
@@ -125,7 +133,7 @@ func CreateFlags(defaultPath string) []cli.Flag {
},
&cli.StringFlag{
Name: flgPath,
EnvVars: []string{envPath},
Sources: cli.EnvVars(envPath),
Usage: "Directory to use for storing the data.",
Value: defaultPath,
},
@@ -222,19 +230,19 @@ func CreateFlags(defaultPath string) []cli.Flag {
&cli.BoolFlag{
Name: flgPFX,
Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.",
EnvVars: []string{envPFX},
Sources: cli.EnvVars(envPFX),
},
&cli.StringFlag{
Name: flgPFXPass,
Usage: "The password used to encrypt the .pfx (PCKS#12) file.",
Value: pkcs12.DefaultPassword,
EnvVars: []string{envPFXPassword},
Sources: cli.EnvVars(envPFXPassword),
},
&cli.StringFlag{
Name: flgPFXFormat,
Usage: "The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256.",
Value: "RC2",
EnvVars: []string{envPFXFormat},
Sources: cli.EnvVars(envPFXFormat),
},
&cli.IntFlag{
Name: flgCertTimeout,
@@ -252,12 +260,3 @@ func CreateFlags(defaultPath string) []cli.Flag {
},
}
}
func getTime(ctx *cli.Context, name string) time.Time {
value := ctx.Timestamp(name)
if value == nil {
return time.Time{}
}
return *value
}

View File

@@ -3,42 +3,34 @@
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/go-acme/lego/v5/cmd"
"github.com/go-acme/lego/v5/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func main() {
app := cli.NewApp()
app.Name = "lego"
app.HelpName = "lego"
app.Usage = "Let's Encrypt client written in Go"
app.EnableBashCompletion = true
app.Version = getVersion()
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("lego version %s %s/%s\n", c.App.Version, runtime.GOOS, runtime.GOARCH)
app := &cli.Command{
Name: "lego",
Usage: "Let's Encrypt client written in Go",
Version: getVersion(),
EnableShellCompletion: true,
Flags: cmd.CreateFlags(""),
Before: cmd.Before,
Commands: cmd.CreateCommands(),
}
var defaultPath string
cwd, err := os.Getwd()
if err == nil {
defaultPath = filepath.Join(cwd, ".lego")
cli.VersionPrinter = func(cmd *cli.Command) {
fmt.Printf("lego version %s %s/%s\n", cmd.Version, runtime.GOOS, runtime.GOARCH)
}
app.Flags = cmd.CreateFlags(defaultPath)
app.Before = cmd.Before
app.Commands = cmd.CreateCommands()
err = app.Run(os.Args)
err := app.Run(context.Background(), os.Args)
if err != nil {
log.Fatal("Error", "error", err)
}

View File

@@ -18,27 +18,27 @@ import (
"github.com/go-acme/lego/v5/log"
"github.com/go-acme/lego/v5/registration"
"github.com/hashicorp/go-retryablehttp"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const filePerm os.FileMode = 0o600
// setupClient creates a new client with challenge settings.
func setupClient(ctx *cli.Context, account *Account, keyType certcrypto.KeyType) *lego.Client {
client := newClient(ctx, account, keyType)
func setupClient(cmd *cli.Command, account *Account, keyType certcrypto.KeyType) *lego.Client {
client := newClient(cmd, account, keyType)
setupChallenges(ctx, client)
setupChallenges(cmd, client)
return client
}
func setupAccount(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, certcrypto.KeyType) {
keyType := getKeyType(ctx)
func setupAccount(ctx context.Context, cmd *cli.Command, accountsStorage *AccountsStorage) (*Account, certcrypto.KeyType) {
keyType := getKeyType(cmd)
privateKey := accountsStorage.GetPrivateKey(keyType)
var account *Account
if accountsStorage.ExistsAccountFilePath() {
account = accountsStorage.LoadAccount(privateKey)
account = accountsStorage.LoadAccount(ctx, privateKey)
} else {
account = &Account{Email: accountsStorage.GetEmail(), key: privateKey}
}
@@ -46,23 +46,23 @@ func setupAccount(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account,
return account, keyType
}
func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
func newClient(cmd *cli.Command, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
config := lego.NewConfig(acc)
config.CADirURL = ctx.String(flgServer)
config.CADirURL = cmd.String(flgServer)
config.Certificate = lego.CertificateConfig{
KeyType: keyType,
Timeout: time.Duration(ctx.Int(flgCertTimeout)) * time.Second,
OverallRequestLimit: ctx.Int(flgOverallRequestLimit),
EnableCommonName: !ctx.Bool(flgDisableCommonName),
Timeout: time.Duration(cmd.Int(flgCertTimeout)) * time.Second,
OverallRequestLimit: cmd.Int(flgOverallRequestLimit),
EnableCommonName: !cmd.Bool(flgDisableCommonName),
}
config.UserAgent = getUserAgent(ctx)
config.UserAgent = getUserAgent(cmd)
if ctx.IsSet(flgHTTPTimeout) {
config.HTTPClient.Timeout = time.Duration(ctx.Int(flgHTTPTimeout)) * time.Second
if cmd.IsSet(flgHTTPTimeout) {
config.HTTPClient.Timeout = time.Duration(cmd.Int(flgHTTPTimeout)) * time.Second
}
if ctx.Bool(flgTLSSkipVerify) {
if cmd.Bool(flgTLSSkipVerify) {
defaultTransport, ok := config.HTTPClient.Transport.(*http.Transport)
if ok { // This is always true because the default client used by the CLI defined the transport.
tr := defaultTransport.Clone()
@@ -88,7 +88,7 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy
log.Fatal("Could not create client.", "error", err)
}
if client.GetExternalAccountRequired() && !ctx.IsSet(flgEAB) {
if client.GetExternalAccountRequired() && !cmd.IsSet(flgEAB) {
log.Fatal(fmt.Sprintf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC))
}
@@ -96,8 +96,8 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy
}
// getKeyType the type from which private keys should be generated.
func getKeyType(ctx *cli.Context) certcrypto.KeyType {
keyType := ctx.String(flgKeyType)
func getKeyType(cmd *cli.Command) certcrypto.KeyType {
keyType := cmd.String(flgKeyType)
switch strings.ToUpper(keyType) {
case "RSA2048":
return certcrypto.RSA2048
@@ -118,8 +118,8 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType {
return ""
}
func getUserAgent(ctx *cli.Context) string {
return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String(flgUserAgent), ctx.App.Version))
func getUserAgent(cmd *cli.Command) string {
return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", cmd.String(flgUserAgent), cmd.Version))
}
func createNonExistingFolder(path string) error {

View File

@@ -16,30 +16,30 @@ import (
"github.com/go-acme/lego/v5/providers/http/memcached"
"github.com/go-acme/lego/v5/providers/http/s3"
"github.com/go-acme/lego/v5/providers/http/webroot"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func setupChallenges(ctx *cli.Context, client *lego.Client) {
if !ctx.Bool(flgHTTP) && !ctx.Bool(flgTLS) && !ctx.IsSet(flgDNS) {
func setupChallenges(cmd *cli.Command, client *lego.Client) {
if !cmd.Bool(flgHTTP) && !cmd.Bool(flgTLS) && !cmd.IsSet(flgDNS) {
log.Fatal(fmt.Sprintf("No challenge selected. You must specify at least one challenge: `--%s`, `--%s`, `--%s`.", flgHTTP, flgTLS, flgDNS))
}
if ctx.Bool(flgHTTP) {
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx), http01.SetDelay(ctx.Duration(flgHTTPDelay)))
if cmd.Bool(flgHTTP) {
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(cmd), http01.SetDelay(cmd.Duration(flgHTTPDelay)))
if err != nil {
log.Fatal("Could not set HTTP challenge provider.", "error", err)
}
}
if ctx.Bool(flgTLS) {
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx), tlsalpn01.SetDelay(ctx.Duration(flgTLSDelay)))
if cmd.Bool(flgTLS) {
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(cmd), tlsalpn01.SetDelay(cmd.Duration(flgTLSDelay)))
if err != nil {
log.Fatal("Could not set TLS challenge provider.", "error", err)
}
}
if ctx.IsSet(flgDNS) {
err := setupDNS(ctx, client)
if cmd.IsSet(flgDNS) {
err := setupDNS(cmd, client)
if err != nil {
log.Fatal("Could not set DNS challenge provider.", "error", err)
}
@@ -47,42 +47,42 @@ func setupChallenges(ctx *cli.Context, client *lego.Client) {
}
//nolint:gocyclo // the complexity is expected.
func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
func setupHTTPProvider(cmd *cli.Command) challenge.Provider {
switch {
case ctx.IsSet(flgHTTPWebroot):
ps, err := webroot.NewHTTPProvider(ctx.String(flgHTTPWebroot))
case cmd.IsSet(flgHTTPWebroot):
ps, err := webroot.NewHTTPProvider(cmd.String(flgHTTPWebroot))
if err != nil {
log.Fatal("Could not create the webroot provider.",
"flag", flgHTTPWebroot, "webRoot", ctx.String(flgHTTPWebroot), "error", err)
"flag", flgHTTPWebroot, "webRoot", cmd.String(flgHTTPWebroot), "error", err)
}
return ps
case ctx.IsSet(flgHTTPMemcachedHost):
ps, err := memcached.NewMemcachedProvider(ctx.StringSlice(flgHTTPMemcachedHost))
case cmd.IsSet(flgHTTPMemcachedHost):
ps, err := memcached.NewMemcachedProvider(cmd.StringSlice(flgHTTPMemcachedHost))
if err != nil {
log.Fatal("Could not create the memcached provider.",
"flag", flgHTTPMemcachedHost, "memcachedHosts", strings.Join(ctx.StringSlice(flgHTTPMemcachedHost), ", "), "error", err)
"flag", flgHTTPMemcachedHost, "memcachedHosts", strings.Join(cmd.StringSlice(flgHTTPMemcachedHost), ", "), "error", err)
}
return ps
case ctx.IsSet(flgHTTPS3Bucket):
ps, err := s3.NewHTTPProvider(ctx.String(flgHTTPS3Bucket))
case cmd.IsSet(flgHTTPS3Bucket):
ps, err := s3.NewHTTPProvider(cmd.String(flgHTTPS3Bucket))
if err != nil {
log.Fatal("Could not create the S3 provider.",
"flag", flgHTTPS3Bucket, "bucket", ctx.String(flgHTTPS3Bucket), "error", err)
"flag", flgHTTPS3Bucket, "bucket", cmd.String(flgHTTPS3Bucket), "error", err)
}
return ps
case ctx.IsSet(flgHTTPPort):
iface := ctx.String(flgHTTPPort)
case cmd.IsSet(flgHTTPPort):
iface := cmd.String(flgHTTPPort)
if !strings.Contains(iface, ":") {
log.Fatal(
fmt.Sprintf("The --%s switch only accepts interface:port or :port for its argument.", flgHTTPPort),
"flag", flgHTTPPort, "port", ctx.String(flgHTTPPort),
"flag", flgHTTPPort, "port", cmd.String(flgHTTPPort),
)
}
@@ -97,20 +97,20 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
Address: net.JoinHostPort(host, port),
})
if header := ctx.String(flgHTTPProxyHeader); header != "" {
if header := cmd.String(flgHTTPProxyHeader); header != "" {
srv.SetProxyHeader(header)
}
return srv
case ctx.Bool(flgHTTP):
case cmd.Bool(flgHTTP):
srv := http01.NewProviderServerWithOptions(http01.Options{
// TODO(ldez): set network stack
Network: "tcp",
Address: net.JoinHostPort("", ":80"),
})
if header := ctx.String(flgHTTPProxyHeader); header != "" {
if header := cmd.String(flgHTTPProxyHeader); header != "" {
srv.SetProxyHeader(header)
}
@@ -122,10 +122,10 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
}
}
func setupTLSProvider(ctx *cli.Context) challenge.Provider {
func setupTLSProvider(cmd *cli.Command) challenge.Provider {
switch {
case ctx.IsSet(flgTLSPort):
iface := ctx.String(flgTLSPort)
case cmd.IsSet(flgTLSPort):
iface := cmd.String(flgTLSPort)
if !strings.Contains(iface, ":") {
log.Fatal(fmt.Sprintf("The --%s switch only accepts interface:port or :port for its argument.", flgTLSPort))
}
@@ -142,7 +142,7 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
Port: port,
})
case ctx.Bool(flgTLS):
case cmd.Bool(flgTLS):
return tlsalpn01.NewProviderServerWithOptions(tlsalpn01.Options{
// TODO(ldez): set network stack
Network: "tcp",
@@ -154,62 +154,62 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
}
}
func setupDNS(ctx *cli.Context, client *lego.Client) error {
err := checkPropagationExclusiveOptions(ctx)
func setupDNS(cmd *cli.Command, client *lego.Client) error {
err := checkPropagationExclusiveOptions(cmd)
if err != nil {
return err
}
wait := ctx.Duration(flgDNSPropagationWait)
wait := cmd.Duration(flgDNSPropagationWait)
if wait < 0 {
return fmt.Errorf("'%s' cannot be negative", flgDNSPropagationWait)
}
provider, err := dns.NewDNSChallengeProviderByName(ctx.String(flgDNS))
provider, err := dns.NewDNSChallengeProviderByName(cmd.String(flgDNS))
if err != nil {
return err
}
opts := &dns01.Options{RecursiveNameservers: ctx.StringSlice(flgDNSResolvers)}
opts := &dns01.Options{RecursiveNameservers: cmd.StringSlice(flgDNSResolvers)}
if ctx.IsSet(flgDNSTimeout) {
opts.Timeout = time.Duration(ctx.Int(flgDNSTimeout)) * time.Second
if cmd.IsSet(flgDNSTimeout) {
opts.Timeout = time.Duration(cmd.Int(flgDNSTimeout)) * time.Second
}
dns01.SetDefaultClient(dns01.NewClient(opts))
err = client.Challenge.SetDNS01Provider(provider,
dns01.CondOption(ctx.Bool(flgDNSDisableCP) || ctx.Bool(flgDNSPropagationDisableANS),
dns01.CondOption(cmd.Bool(flgDNSDisableCP) || cmd.Bool(flgDNSPropagationDisableANS),
dns01.DisableAuthoritativeNssPropagationRequirement()),
dns01.CondOption(ctx.Duration(flgDNSPropagationWait) > 0,
dns01.CondOption(cmd.Duration(flgDNSPropagationWait) > 0,
// TODO(ldez): inside the next major version we will use flgDNSDisableCP here.
// This will change the meaning of this flag to really disable all propagation checks.
dns01.PropagationWait(wait, true)),
dns01.CondOption(ctx.Bool(flgDNSPropagationRNS),
dns01.CondOption(cmd.Bool(flgDNSPropagationRNS),
dns01.RecursiveNSsPropagationRequirement()),
)
return err
}
func checkPropagationExclusiveOptions(ctx *cli.Context) error {
if ctx.IsSet(flgDNSDisableCP) {
func checkPropagationExclusiveOptions(cmd *cli.Command) error {
if cmd.IsSet(flgDNSDisableCP) {
log.Warnf(log.LazySprintf("The flag '%s' is deprecated use '%s' instead.", flgDNSDisableCP, flgDNSPropagationDisableANS))
}
if (isSetBool(ctx, flgDNSDisableCP) || isSetBool(ctx, flgDNSPropagationDisableANS)) && ctx.IsSet(flgDNSPropagationWait) {
if (isSetBool(cmd, flgDNSDisableCP) || isSetBool(cmd, flgDNSPropagationDisableANS)) && cmd.IsSet(flgDNSPropagationWait) {
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSPropagationDisableANS, flgDNSPropagationWait)
}
if isSetBool(ctx, flgDNSPropagationRNS) && ctx.IsSet(flgDNSPropagationWait) {
if isSetBool(cmd, flgDNSPropagationRNS) && cmd.IsSet(flgDNSPropagationWait) {
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSPropagationRNS, flgDNSPropagationWait)
}
return nil
}
func isSetBool(ctx *cli.Context, name string) bool {
return ctx.IsSet(name) && ctx.Bool(name)
func isSetBool(cmd *cli.Command, name string) bool {
return cmd.IsSet(name) && cmd.Bool(name)
}

View File

@@ -8,7 +8,7 @@ NAME:
lego - Let's Encrypt client written in Go
USAGE:
lego [global options] command [command options]
lego [global options] [command [command options]]
COMMANDS:
run Register an account, then create and install a certificate
@@ -19,45 +19,45 @@ COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--domains value, -d value [ --domains value, -d value ] Add a domain to the process. Can be specified multiple times.
--server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") [$LEGO_SERVER]
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false)
--email value, -m value Email used for registration and recovery contact. [$LEGO_EMAIL]
--disable-cn Disable the use of the common name in the CSR. (default: false)
--csr value, -c value Certificate signing request filename, if an external CSR is to be used.
--eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false) [$LEGO_EAB]
--kid value Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID]
--hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. [$LEGO_EAB_HMAC]
--key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384. (default: "ec256")
--filename value (deprecated) Filename of the generated certificate.
--path value Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH]
--http Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges. (default: false)
--http.port value Set the port and interface to use for HTTP-01 based challenges to listen on. Supported: interface:port or :port. (default: ":80")
--http.delay value Delay between the starts of the HTTP server (use for HTTP-01 based challenges) and the validation of the challenge. (default: 0s)
--http.proxy-header value Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy. (default: "Host")
--http.webroot value Set the webroot folder to use for HTTP-01 based challenges to write directly to the .well-known/acme-challenge file. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge
--http.memcached-host value [ --http.memcached-host value ] Set the memcached host(s) to use for HTTP-01 based challenges. Challenges will be written to all specified hosts.
--http.s3-bucket value Set the S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket.
--tls Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges. (default: false)
--tls.port value Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port. (default: ":443")
--tls.delay value Delay between the start of the TLS listener (use for TLSALPN-01 based challenges) and the validation of the challenge. (default: 0s)
--dns value Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.
--dns.disable-cp (deprecated) use dns.propagation-disable-ans instead. (default: false)
--dns.propagation-disable-ans By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers. (default: false)
--dns.propagation-rns By setting this flag to true, use all the recursive nameservers to check the propagation of the TXT record. (default: false)
--dns.propagation-wait value By setting this flag, disables all the propagation checks of the TXT record and uses a wait duration instead. (default: 0s)
--dns.resolvers value [ --dns.resolvers value ] Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination. For DNS-01 challenge verification, the authoritative DNS server is queried directly. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0)
--tls-skip-verify Skip the TLS verification of the ACME server. (default: false)
--dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10)
--pem Generate an additional .pem (base64) file by concatenating the .key and .crt files together. (default: false)
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. (default: false) [$LEGO_PFX]
--pfx.pass value The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit") [$LEGO_PFX_PASSWORD]
--pfx.format value The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256. (default: "RC2") [$LEGO_PFX_FORMAT]
--cert.timeout value Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--overall-request-limit value ACME overall requests limit. (default: 18)
--user-agent value Add to the user-agent sent to the CA to identify an application embedding lego-cli
--help, -h show help
--domains string, -d string [ --domains string, -d string ] Add a domain to the process. Can be specified multiple times.
--server string, -s string CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") [$LEGO_SERVER]
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.
--email string, -m string Email used for registration and recovery contact. [$LEGO_EMAIL]
--disable-cn Disable the use of the common name in the CSR.
--csr string, -c string Certificate signing request filename, if an external CSR is to be used.
--eab Use External Account Binding for account registration. Requires --kid and --hmac. [$LEGO_EAB]
--kid string Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID]
--hmac string MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. [$LEGO_EAB_HMAC]
--key-type string, -k string Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384. (default: "ec256")
--filename string (deprecated) Filename of the generated certificate.
--path string Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH]
--http Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges.
--http.port string Set the port and interface to use for HTTP-01 based challenges to listen on. Supported: interface:port or :port. (default: ":80")
--http.delay duration Delay between the starts of the HTTP server (use for HTTP-01 based challenges) and the validation of the challenge. (default: 0s)
--http.proxy-header string Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy. (default: "Host")
--http.webroot string Set the webroot folder to use for HTTP-01 based challenges to write directly to the .well-known/acme-challenge file. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge
--http.memcached-host string [ --http.memcached-host string ] Set the memcached host(s) to use for HTTP-01 based challenges. Challenges will be written to all specified hosts.
--http.s3-bucket string Set the S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket.
--tls Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges.
--tls.port string Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port. (default: ":443")
--tls.delay duration Delay between the start of the TLS listener (use for TLSALPN-01 based challenges) and the validation of the challenge. (default: 0s)
--dns string Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.
--dns.disable-cp (deprecated) use dns.propagation-disable-ans instead.
--dns.propagation-disable-ans By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers.
--dns.propagation-rns By setting this flag to true, use all the recursive nameservers to check the propagation of the TXT record.
--dns.propagation-wait duration By setting this flag, disables all the propagation checks of the TXT record and uses a wait duration instead. (default: 0s)
--dns.resolvers string [ --dns.resolvers string ] Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination. For DNS-01 challenge verification, the authoritative DNS server is queried directly. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--http-timeout int Set the HTTP timeout value to a specific value in seconds. (default: 0)
--tls-skip-verify Skip the TLS verification of the ACME server.
--dns-timeout int Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10)
--pem Generate an additional .pem (base64) file by concatenating the .key and .crt files together.
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. [$LEGO_PFX]
--pfx.pass string The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit") [$LEGO_PFX_PASSWORD]
--pfx.format string The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256. (default: "RC2") [$LEGO_PFX_FORMAT]
--cert.timeout int Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--overall-request-limit int ACME overall requests limit. (default: 18)
--user-agent string Add to the user-agent sent to the CA to identify an application embedding lego-cli
--help, -h show help
"""
[[command]]
@@ -67,20 +67,20 @@ NAME:
lego run - Register an account, then create and install a certificate
USAGE:
lego run [command options]
lego run
OPTIONS:
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
--not-before value Set the notBefore field in the certificate (RFC3339 format)
--not-after value Set the notAfter field in the certificate (RFC3339 format)
--private-key value Path to private key (in PEM encoding) for the certificate. By default, the private key is generated.
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--profile value If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.
--always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful.
--run-hook value Define a hook. The hook is executed when the certificates are effectively created.
--run-hook-timeout value Define the timeout for the hook execution. (default: 2m0s)
--help, -h show help
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate.
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.
--not-before time Set the notBefore field in the certificate (RFC3339 format)
--not-after time Set the notAfter field in the certificate (RFC3339 format)
--private-key string Path to private key (in PEM encoding) for the certificate. By default, the private key is generated.
--preferred-chain string If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--profile string If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.
--always-deactivate-authorizations string Force the authorizations to be relinquished even if the certificate request was successful.
--run-hook string Define a hook. The hook is executed when the certificates are effectively created.
--run-hook-timeout duration Define the timeout for the hook execution. (default: 2m0s)
--help, -h show help
"""
[[command]]
@@ -90,26 +90,26 @@ NAME:
lego renew - Renew a certificate
USAGE:
lego renew [command options]
lego renew
OPTIONS:
--days value The number of days left on a certificate to renew it. (default: 30)
--dynamic Compute dynamically, based on the lifetime of the certificate(s), when to renew: use 1/3rd of the lifetime left, or 1/2 of the lifetime for short-lived certificates). This supersedes --days and will be the default behavior in Lego v5. (default: false)
--ari-disable Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed. (default: false)
--ari-wait-to-renew-duration value The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s)
--reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false)
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false)
--not-before value Set the notBefore field in the certificate (RFC3339 format)
--not-after value Set the notAfter field in the certificate (RFC3339 format)
--preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--profile value If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.
--always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful.
--renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed.
--renew-hook-timeout value Define the timeout for the hook execution. (default: 2m0s)
--no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false)
--force-cert-domains Check and ensure that the cert's domain list matches those passed in the domains argument. (default: false)
--help, -h show help
--days int The number of days left on a certificate to renew it. (default: 30)
--dynamic Compute dynamically, based on the lifetime of the certificate(s), when to renew: use 1/3rd of the lifetime left, or 1/2 of the lifetime for short-lived certificates). This supersedes --days and will be the default behavior in Lego v5.
--ari-disable Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed.
--ari-wait-to-renew-duration duration The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s)
--reuse-key Used to indicate you want to reuse your current private key for the new certificate.
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate.
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.
--not-before time Set the notBefore field in the certificate (RFC3339 format)
--not-after time Set the notAfter field in the certificate (RFC3339 format)
--preferred-chain string If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--profile string If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.
--always-deactivate-authorizations string Force the authorizations to be relinquished even if the certificate request was successful.
--renew-hook string Define a hook. The hook is executed only when the certificates are effectively renewed.
--renew-hook-timeout duration Define the timeout for the hook execution. (default: 2m0s)
--no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way.
--force-cert-domains Check and ensure that the cert's domain list matches those passed in the domains argument.
--help, -h show help
"""
[[command]]
@@ -119,12 +119,12 @@ NAME:
lego revoke - Revoke a certificate
USAGE:
lego revoke [command options]
lego revoke
OPTIONS:
--keep, -k Keep the certificates after the revocation instead of archiving them. (default: false)
--reason value Identifies the reason for the certificate revocation. See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1. Valid values are: 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged), 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL), 9 (privilegeWithdrawn), or 10 (aACompromise). (default: 0)
--help, -h show help
--keep, -k Keep the certificates after the revocation instead of archiving them.
--reason uint Identifies the reason for the certificate revocation. See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1. Valid values are: 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged), 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL), 9 (privilegeWithdrawn), or 10 (aACompromise). (default: 0)
--help, -h show help
"""
[[command]]
@@ -134,11 +134,11 @@ NAME:
lego list - Display certificates and accounts information.
USAGE:
lego list [command options]
lego list
OPTIONS:
--accounts, -a Display accounts. (default: false)
--names, -n Display certificate common names only. (default: false)
--accounts, -a Display accounts.
--names, -n Display certificate common names only.
--help, -h show help
"""

5
go.mod
View File

@@ -84,7 +84,7 @@ require (
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.28
github.com/transip/gotransip/v6 v6.26.1
github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419
github.com/urfave/cli/v2 v2.27.7
github.com/urfave/cli/v3 v3.6.2
github.com/vinyldns/go-vinyldns v0.9.17
github.com/volcengine/volc-sdk-golang v1.0.233
github.com/vultr/govultr/v3 v3.26.1
@@ -136,7 +136,6 @@ require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/fatih/color v1.16.0 // indirect
@@ -183,7 +182,6 @@ require (
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sacloud/go-http v0.1.9 // indirect
github.com/sacloud/packages-go v0.0.12 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
@@ -200,7 +198,6 @@ require (
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.mongodb.org/mongo-driver v1.13.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect

10
go.sum
View File

@@ -259,8 +259,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -815,8 +813,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sqBnCKDs=
github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo=
@@ -918,8 +914,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 h1:/VaznPrb/b68e3iMvkr27fU7JqPKU4j7tIITZnjQX1k=
github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419/go.mod h1:QN0/PdenvYWB0GRMz6JJbPeZz2Lph2iys1p8AFVHm2c=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/vinyldns/go-vinyldns v0.9.17 h1:hfPZfCaxcRBX6Gsgl42rLCeoal58/BH8kkvJShzjjdI=
github.com/vinyldns/go-vinyldns v0.9.17/go.mod h1:pwWhE9K/leGDOIduVhRGvQ3ecVMHWRfEnKYUTEU3gB4=
github.com/volcengine/volc-sdk-golang v1.0.233 h1:Hh2pzwu/Wq19rsZgNo3HdpjQB28D/F0+m6EjLVggmhM=
@@ -932,8 +928,6 @@ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3k
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yandex-cloud/go-genproto v0.43.0 h1:HjBesEmCN8ZOhjjh8gs605vvi9/MBJAW3P20OJ4iQnw=
github.com/yandex-cloud/go-genproto v0.43.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
github.com/yandex-cloud/go-sdk/services/dns v0.0.25 h1:BcGEuOnwq2X3LS2kvFC6BOdZkOq4Lc7XAYvzap/SJJY=

View File

@@ -4,6 +4,7 @@ package main
import (
"bytes"
"context"
"fmt"
"log"
"os"
@@ -11,7 +12,7 @@ import (
"text/template"
"github.com/go-acme/lego/v5/cmd"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const outputFile = "../../docs/data/zz_cli_help.toml"
@@ -35,7 +36,7 @@ type commandHelp struct {
func main() {
log.SetFlags(0)
err := generate()
err := generate(context.Background())
if err != nil {
log.Fatal(err)
}
@@ -43,7 +44,7 @@ func main() {
log.Println("cli_help.toml updated")
}
func generate() error {
func generate(ctx context.Context) error {
app := createStubApp()
outputTpl := template.Must(template.New("output").Parse(baseTemplate))
@@ -59,7 +60,7 @@ func generate() error {
{"lego", "help", "list"},
{"lego", "dnshelp"},
} {
content, err := run(app, args)
content, err := run(ctx, app, args)
if err != nil {
return fmt.Errorf("running %s failed: %w", args, err)
}
@@ -88,18 +89,16 @@ func generate() error {
// - substitute "." for CWD in default config path, as the user will very likely see a different path
// - do not include version information, because we're likely running against a snapshot
// - skip DNS help and provider list, as initialization takes time, and we don't generate `lego dns --help` here.
func createStubApp() *cli.App {
app := cli.NewApp()
app.Name = "lego"
app.HelpName = "lego"
app.Usage = "Let's Encrypt client written in Go"
app.Flags = cmd.CreateFlags("./.lego")
app.Commands = cmd.CreateCommands()
return app
func createStubApp() *cli.Command {
return &cli.Command{
Name: "lego",
Usage: "Let's Encrypt client written in Go",
Flags: cmd.CreateFlags("./.lego"),
Commands: cmd.CreateCommands(),
}
}
func run(app *cli.App, args []string) (h commandHelp, err error) {
func run(ctx context.Context, app *cli.Command, args []string) (h commandHelp, err error) {
w := app.Writer
defer func() { app.Writer = w }()
@@ -108,7 +107,11 @@ func run(app *cli.App, args []string) (h commandHelp, err error) {
app.Writer = &buf
if err := app.Run(args); err != nil {
if app.Command(args[1]) != nil {
app.Command(args[1]).Writer = app.Writer
}
if err := app.Run(ctx, args); err != nil {
return h, err
}

View File

@@ -1,6 +1,7 @@
package main
import (
"context"
"fmt"
"go/ast"
"go/parser"
@@ -10,7 +11,7 @@ import (
"strconv"
hcversion "github.com/hashicorp/go-version"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const flgMode = "mode"
@@ -29,48 +30,48 @@ const (
)
func main() {
app := cli.NewApp()
app.Name = "lego-releaser"
app.Usage = "Lego releaser"
app.HelpName = "releaser"
app.Commands = []*cli.Command{
{
Name: "release",
Usage: "Update file for a release",
Action: release,
Before: func(ctx *cli.Context) error {
mode := ctx.String("mode")
switch mode {
case modePatch, modeMinor, modeMajor:
return nil
default:
return fmt.Errorf("invalid mode: %s", mode)
}
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: flgMode,
Aliases: []string{"m"},
Value: modePatch,
Usage: fmt.Sprintf("The release mode: %s|%s|%s", modePatch, modeMinor, modeMajor),
app := cli.Command{
Name: "lego-releaser",
Usage: "Lego releaser",
Commands: []*cli.Command{
{
Name: "release",
Usage: "Update file for a release",
Action: release,
Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
mode := cmd.String("mode")
switch mode {
case modePatch, modeMinor, modeMajor:
return ctx, nil
default:
return ctx, fmt.Errorf("invalid mode: %s", mode)
}
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: flgMode,
Aliases: []string{"m"},
Value: modePatch,
Usage: fmt.Sprintf("The release mode: %s|%s|%s", modePatch, modeMinor, modeMajor),
},
},
},
},
{
Name: "detach",
Usage: "Update file post release",
Action: detach,
{
Name: "detach",
Usage: "Update file post release",
Action: detach,
},
},
}
err := app.Run(os.Args)
err := app.Run(context.Background(), os.Args)
if err != nil {
log.Fatal(err)
}
}
func release(ctx *cli.Context) error {
mode := ctx.String(flgMode)
func release(ctx context.Context, cmd *cli.Command) error {
mode := cmd.String(flgMode)
currentVersion, err := readCurrentVersion(versionSourceFile)
if err != nil {
@@ -90,7 +91,7 @@ func release(ctx *cli.Context) error {
return nil
}
func detach(_ *cli.Context) error {
func detach(_ context.Context, _ *cli.Command) error {
currentVersion, err := readCurrentVersion(versionSourceFile)
if err != nil {
return fmt.Errorf("read current version: %w", err)