@@ -119,6 +119,9 @@ const DigitalOceanDNSProvider = "digitalocean"
119119// CloudflareDNSProvider represents the Cloudflare DNS provider.
120120const CloudflareDNSProvider = "cloudflare"
121121
122+ // AcmeDNSProvider represents the acme-dns provider for DNS-01 challenges.
123+ const AcmeDNSProvider = "acmedns"
124+
122125// Config holds the configuration for CertBot operations.
123126type Config struct {
124127 // RootDir is the root directory where CertBot stores its configurations
@@ -298,22 +301,35 @@ type WebEndpointsCertificate struct {
298301 Domain string
299302 // Provider is the DNS provider used for DNS-01 challenges.
300303 Provider DNSProvider
301- // Token is the API token for the DNS provider.
304+ // Token is the API token for the DNS provider (used for Cloudflare and DigitalOcean) .
302305 Token string
306+ // AcmeDNSURL is the URL of the acme-dns server (only for acmedns provider).
307+ AcmeDNSURL string
308+ // AcmeDNSUsername is the username from acme-dns registration (only for acmedns provider).
309+ AcmeDNSUsername string
310+ // AcmeDNSPassword is the password from acme-dns registration (only for acmedns provider).
311+ AcmeDNSPassword string
312+ // AcmeDNSSubdomain is the subdomain from acme-dns registration (only for acmedns provider).
313+ AcmeDNSSubdomain string
303314
304315 ex Executor
305316 fs afero.Fs
306317}
307318
308319// NewWebEndpointsCertificate creates a new TunnelsCertificate instance for generating
309320// wildcard certificates using DNS-01 challenges.
310- func NewWebEndpointsCertificate (domain string , provider DNSProvider , token string ) Certificate {
321+ func NewWebEndpointsCertificate (domain string , provider DNSProvider , token string , acmeDNSURL , acmeDNSUsername , acmeDNSPassword , acmeDNSSubdomain string ) Certificate {
311322 return & WebEndpointsCertificate {
312323 Domain : domain ,
313324
314325 Provider : provider ,
315326 Token : token ,
316327
328+ AcmeDNSURL : acmeDNSURL ,
329+ AcmeDNSUsername : acmeDNSUsername ,
330+ AcmeDNSPassword : acmeDNSPassword ,
331+ AcmeDNSSubdomain : acmeDNSSubdomain ,
332+
317333 ex : NewExecutor (),
318334 fs : afero .NewOsFs (),
319335 }
@@ -322,22 +338,46 @@ func NewWebEndpointsCertificate(domain string, provider DNSProvider, token strin
322338// generateProviderCredentialsFile creates a credentials file for the DNS provider.
323339// This file contains the API token needed for DNS-01 challenges.
324340func (d * WebEndpointsCertificate ) generateProviderCredentialsFile () (afero.File , error ) {
325- tokenLine := fmt .Sprintf ("dns_%s_token = %s" , d .Provider , d .Token )
326-
327- // Certbot Cloudflare plugin expects dns_cloudflare_api_token
328- if d .Provider == CloudflareDNSProvider {
329- tokenLine = fmt .Sprintf ("dns_cloudflare_api_token = %s" , d .Token )
330- }
331-
332- file , err := d .fs .Create (fmt .Sprintf ("/etc/shellhub-gateway/%s.ini" , string (d .Provider )))
341+ var content string
342+ var filename string
343+
344+ switch d .Provider {
345+ case CloudflareDNSProvider :
346+ // Certbot Cloudflare plugin expects dns_cloudflare_api_token
347+ content = fmt .Sprintf ("dns_cloudflare_api_token = %s" , d .Token )
348+ filename = "/etc/shellhub-gateway/cloudflare.ini"
349+
350+ case DigitalOceanDNSProvider :
351+ content = fmt .Sprintf ("dns_digitalocean_token = %s" , d .Token )
352+ filename = "/etc/shellhub-gateway/digitalocean.ini"
353+
354+ case AcmeDNSProvider :
355+ // certbot-dns-acmedns expects a JSON file with acme-dns credentials
356+ content = fmt .Sprintf (`{
357+ "%s": {
358+ "username": "%s",
359+ "password": "%s",
360+ "fulldomain": "%s",
361+ "subdomain": "%s",
362+ "allowfrom": []
363+ }
364+ }` , d .Domain , d .AcmeDNSUsername , d .AcmeDNSPassword ,
365+ fmt .Sprintf ("_acme-challenge.%s" , d .Domain ), d .AcmeDNSSubdomain )
366+ filename = "/etc/shellhub-gateway/acmedns.json"
367+
368+ default :
369+ return nil , fmt .Errorf ("unsupported DNS provider: %s" , d .Provider )
370+ }
371+
372+ file , err := d .fs .Create (filename )
333373 if err != nil {
334- log .WithError (err ).Error ("failed to create shellhub-gateway file with dns provider token " )
374+ log .WithError (err ).WithField ( "filename" , filename ). Error ("failed to create credentials file" )
335375
336376 return nil , err
337377 }
338378
339- if _ , err := file .Write ([]byte (tokenLine )); err != nil {
340- log .WithError (err ).Error ("failed to write the token into credentials file" )
379+ if _ , err := file .Write ([]byte (content )); err != nil {
380+ log .WithError (err ).Error ("failed to write credentials to file" )
341381
342382 return nil , err
343383 }
@@ -354,8 +394,24 @@ func (d *WebEndpointsCertificate) Check() error {
354394 return errors .New ("DNS provider is required for certificate generation" )
355395 }
356396
357- if d .Token == "" {
358- return errors .New ("DNS provider token is required for certificate generation" )
397+ // Validate provider-specific credentials
398+ switch d .Provider {
399+ case CloudflareDNSProvider , DigitalOceanDNSProvider :
400+ if d .Token == "" {
401+ return fmt .Errorf ("DNS provider token is required for %s" , d .Provider )
402+ }
403+ case AcmeDNSProvider :
404+ if d .AcmeDNSUsername == "" {
405+ return errors .New ("acme-dns username is required for acmedns provider" )
406+ }
407+ if d .AcmeDNSPassword == "" {
408+ return errors .New ("acme-dns password is required for acmedns provider" )
409+ }
410+ if d .AcmeDNSSubdomain == "" {
411+ return errors .New ("acme-dns subdomain is required for acmedns provider" )
412+ }
413+ default :
414+ return fmt .Errorf ("unsupported DNS provider: %s" , d .Provider )
359415 }
360416
361417 if _ , err := d .fs .Stat ("/etc/shellhub-gateway" ); os .IsNotExist (err ) {
@@ -384,7 +440,7 @@ func (d *WebEndpointsCertificate) Generate(staging bool) error {
384440 // Create the DNS provider credentials file
385441 file , err := d .generateProviderCredentialsFile ()
386442 if err != nil {
387- log .WithError (err ).Error ("failed to generate INI file" )
443+ log .WithError (err ).Error ("failed to generate credentials file" )
388444
389445 return err
390446 }
@@ -397,13 +453,28 @@ func (d *WebEndpointsCertificate) Generate(staging bool) error {
397453 "--register-unsafely-without-email" ,
398454 "--cert-name" ,
399455 fmt .Sprintf ("*.%s" , d .Domain ),
400- fmt .Sprintf ("--dns-%s" , d .Provider ),
401- fmt .Sprintf ("--dns-%s-credentials" , d .Provider ),
402- file .Name (),
403- "-d" ,
404- fmt .Sprintf ("*.%s" , d .Domain ),
405456 }
406457
458+ // Add provider-specific arguments
459+ if d .Provider == AcmeDNSProvider {
460+ // certbot-dns-acmedns uses different flags
461+ args = append (args ,
462+ "--dns-acmedns" ,
463+ "--dns-acmedns-credentials" ,
464+ file .Name (),
465+ )
466+ } else {
467+ // Standard certbot DNS plugins (cloudflare, digitalocean)
468+ args = append (args ,
469+ fmt .Sprintf ("--dns-%s" , d .Provider ),
470+ fmt .Sprintf ("--dns-%s-credentials" , d .Provider ),
471+ file .Name (),
472+ )
473+ }
474+
475+ // Add domain
476+ args = append (args , "-d" , fmt .Sprintf ("*.%s" , d .Domain ))
477+
407478 if staging {
408479 log .Info ("running generate with staging on dns" )
409480
0 commit comments