@@ -2,7 +2,6 @@ package main
22
33import (
44 "fmt"
5- "log"
65 "net"
76 "net/http"
87 "os"
@@ -11,6 +10,8 @@ import (
1110 "time"
1211
1312 "github.com/pkg/errors"
13+ log "github.com/sirupsen/logrus"
14+ "github.com/spf13/afero"
1415)
1516
1617// DNSProvider represents a DNS provider to generate certificates.
@@ -19,45 +20,67 @@ type DNSProvider string
1920// DigitalOceanDNSProvider represents the Digital Ocean DNS provider.
2021const DigitalOceanDNSProvider = "digitalocean"
2122
22- type tunnels struct {
23- domain string
24- token string
23+ type Tunnels struct {
24+ // Domain is the default domain used to generate certificate for Tunnels.
25+ Domain string
26+ // Provider is the DNS provider used to generate wildcard certificates.
27+ Provider DNSProvider
28+ // Token is a DNS token used to generate wildcard certificates.
29+ Token string
30+ }
31+
32+ type Config struct {
33+ // RootDir is the root directory for CertBot configurations.
34+ RootDir string
35+ // Domain is the default domain used to generate certificate for ShellHub.
36+ Domain string
37+ // Staging defines if the CertBot will use the staging server to generate certificates.
38+ Staging bool
39+ // RenewedCallback is a callback called after certificate renew.
40+ RenewedCallback func ()
41+
42+ Tunnels * Tunnels
2543}
2644
2745// CertBot handles the generation and renewal of SSL certificates.
2846type CertBot struct {
29- rootDir string
30- domain string
31- staging bool
32- renewedCallback func ()
33- tunnels * tunnels
47+ Config * Config
48+
49+ fs afero.Fs
50+ }
51+
52+ func newCertBot (config * Config ) * CertBot {
53+ return & CertBot {
54+ Config : config ,
55+ fs : afero .NewOsFs (),
56+ }
3457}
3558
3659// ensureCertificates checks if the SSL certificate exists and generates it if not.
3760func (cb * CertBot ) ensureCertificates () {
38- certPath := fmt .Sprintf ("%s/live/%s/fullchain.pem" , cb .rootDir , cb .domain )
39- if _ , err := os .Stat (certPath ); os .IsNotExist (err ) {
61+ certPath := fmt .Sprintf ("%s/live/%s/fullchain.pem" , cb .Config . RootDir , cb .Config . Domain )
62+ if _ , err := cb . fs .Stat (certPath ); os .IsNotExist (err ) {
4063 cb .generateCertificate ()
4164 }
4265
43- if cb .tunnels != nil {
66+ if cb .Config . Tunnels != nil {
4467 // NOTE: We are recreating the INI file every time to ensure it has the latest token from the environment.
45- cb .generateProviderCredentialsFile (DigitalOceanDNSProvider )
68+ cb .generateProviderCredentialsFile ()
4669
47- certPath := fmt .Sprintf ("%s/live/*.%s/fullchain.pem" , cb .rootDir , cb .tunnels . domain )
48- if _ , err := os .Stat (certPath ); os .IsNotExist (err ) {
49- cb .generateCertificateFromDNS (DigitalOceanDNSProvider )
70+ certPath := fmt .Sprintf ("%s/live/*.%s/fullchain.pem" , cb .Config . RootDir , cb .Config . Tunnels . Domain )
71+ if _ , err := cb . fs .Stat (certPath ); os .IsNotExist (err ) {
72+ cb .generateCertificateFromDNS ()
5073 }
5174 }
5275}
5376
5477// generateCertificate generates a new SSL certificate using Certbot.
5578func (cb * CertBot ) generateCertificate () {
56- fmt . Println ( "Generating SSL certificate" )
79+ log . Info ( "generating SSL certificate" )
5780
58- challengeDir := fmt .Sprintf ("%s/.well-known/acme-challenge" , cb .rootDir )
59- if err := os .MkdirAll (challengeDir , 0o755 ); err != nil {
60- log .Fatal (err )
81+ challengeDir := fmt .Sprintf ("%s/.well-known/acme-challenge" , cb .Config . RootDir )
82+ if err := cb . fs .MkdirAll (challengeDir , 0o755 ); err != nil {
83+ log .WithError (err ). Fatal ( "failed to create acme challenge on filesystem" )
6184 }
6285
6386 acmeServer := cb .startACMEServer ()
@@ -69,13 +92,15 @@ func (cb *CertBot) generateCertificate() {
6992 "--agree-tos" ,
7093 "--register-unsafely-without-email" ,
7194 "--webroot" ,
72- "--webroot-path" , cb .rootDir ,
95+ "--webroot-path" , cb .Config . RootDir ,
7396 "--preferred-challenges" , "http" ,
7497 "-n" ,
7598 "-d" ,
76- cb .domain ,
99+ cb .Config . Domain ,
77100 )
78- if cb .staging {
101+ if cb .Config .Staging {
102+ log .Info ("running generate with staging" )
103+
79104 cmd .Args = append (cmd .Args , "--staging" )
80105 }
81106 cmd .Stdout , cmd .Stderr = os .Stdout , os .Stderr
@@ -85,12 +110,16 @@ func (cb *CertBot) generateCertificate() {
85110 }
86111
87112 cb .stopACMEServer (acmeServer )
113+
114+ log .Info ("generate run" )
88115}
89116
90- func (cb * CertBot ) generateProviderCredentialsFile (provider DNSProvider ) (* os .File , error ) {
91- token := fmt .Sprintf ("dns_%s_token = %s" , provider , cb .tunnels . token )
92- file , err := os . Create (fmt .Sprintf ("/etc/shellhub-gateway/%s.ini" , string (provider )))
117+ func (cb * CertBot ) generateProviderCredentialsFile () (afero .File , error ) {
118+ token := fmt .Sprintf ("dns_%s_token = %s" , cb . Config . Tunnels . Provider , cb .Config . Tunnels . Token )
119+ file , err := cb . fs . Create (fmt .Sprintf ("/etc/shellhub-gateway/%s.ini" , string (cb . Config . Tunnels . Provider )))
93120 if err != nil {
121+ log .WithError (err ).Error ("failed to create shellhub-gateway file with dns provider token" )
122+
94123 return nil , err
95124 }
96125
@@ -99,12 +128,12 @@ func (cb *CertBot) generateProviderCredentialsFile(provider DNSProvider) (*os.Fi
99128 return file , nil
100129}
101130
102- func (cb * CertBot ) generateCertificateFromDNS (provider DNSProvider ) {
103- fmt . Println ( "Generating SSL certificate with DNS" )
131+ func (cb * CertBot ) generateCertificateFromDNS () {
132+ log . Info ( "generating SSL certificate with DNS" )
104133
105- file , err := cb .generateProviderCredentialsFile (provider )
134+ file , err := cb .generateProviderCredentialsFile ()
106135 if err != nil {
107- log .Fatalf ( "Failed to generate INI file: %v" , err )
136+ log .WithError ( err ). Fatal ( "failed to generate INI file" )
108137 }
109138
110139 cmd := exec .Command ( //nolint:gosec
@@ -114,21 +143,26 @@ func (cb *CertBot) generateCertificateFromDNS(provider DNSProvider) {
114143 "--agree-tos" ,
115144 "--register-unsafely-without-email" ,
116145 "--cert-name" ,
117- fmt .Sprintf ("*.%s" , cb .tunnels . domain ),
118- fmt .Sprintf ("--dns-%s" , provider ),
119- fmt .Sprintf ("--dns-%s-credentials" , provider ),
146+ fmt .Sprintf ("*.%s" , cb .Config . Tunnels . Domain ),
147+ fmt .Sprintf ("--dns-%s" , cb . Config . Tunnels . Provider ),
148+ fmt .Sprintf ("--dns-%s-credentials" , cb . Config . Tunnels . Provider ),
120149 file .Name (),
121150 "-d" ,
122- fmt .Sprintf ("*.%s" , cb .tunnels . domain ),
151+ fmt .Sprintf ("*.%s" , cb .Config . Tunnels . Domain ),
123152 )
124- if cb .staging {
153+ if cb .Config .Staging {
154+ log .Info ("running generate with staging on dns" )
155+
125156 cmd .Args = append (cmd .Args , "--staging" )
126157 }
158+
127159 cmd .Stdout , cmd .Stderr = os .Stdout , os .Stderr
128160
129161 if err := cmd .Run (); err != nil {
130- log .Fatal ("Failed to generate SSL certificate" )
162+ log .WithError ( err ). Fatal ("failed to generate SSL certificate" )
131163 }
164+
165+ log .Info ("generate run on dns" )
132166}
133167
134168// startACMEServer starts a local HTTP server for the ACME challenge.
@@ -139,7 +173,7 @@ func (cb *CertBot) startACMEServer() *http.Server {
139173 http .StripPrefix (
140174 "/.well-known/acme-challenge/" ,
141175 http .FileServer (
142- http .Dir (filepath .Join (cb .rootDir , ".well-known/acme-challenge" )),
176+ http .Dir (filepath .Join (cb .Config . RootDir , ".well-known/acme-challenge" )),
143177 ),
144178 ),
145179 )
@@ -150,12 +184,12 @@ func (cb *CertBot) startACMEServer() *http.Server {
150184
151185 listener , err := net .Listen ("tcp" , ":80" )
152186 if err != nil {
153- log .Fatalf ( "Failed to start ACME server listener: %v" , err )
187+ log .WithError ( err ). Fatal ( "failed to start ACME server listener" )
154188 }
155189
156190 go func () {
157191 if err := server .Serve (listener ); err != nil && ! errors .Is (err , http .ErrServerClosed ) {
158- log .Fatalf ( "ACME server error: %v" , err )
192+ log .WithError ( err ). Fatal ( "acme server error" )
159193 }
160194 }()
161195
@@ -165,7 +199,7 @@ func (cb *CertBot) startACMEServer() *http.Server {
165199// stopACMEServer stops the local ACME server.
166200func (cb * CertBot ) stopACMEServer (server * http.Server ) {
167201 if err := server .Close (); err != nil {
168- log .Fatalf ( "Could not stop ACME server: %v" , err )
202+ log .WithError ( err ). Fatal ( "could not stop ACME server" )
169203 }
170204}
171205
@@ -175,32 +209,37 @@ func (cb *CertBot) executeRenewCertificates() error {
175209 "renew" ,
176210 )
177211
178- if cb .staging {
212+ if cb .Config .Staging {
213+ log .Info ("running renew with staging" )
214+
179215 cmd .Args = append (cmd .Args , "--staging" )
180216 }
181217
182218 if err := cmd .Run (); err != nil {
183- log .Println ( "Failed to renew SSL certificate" )
219+ log .WithError ( err ). Error ( "failed to renew SSL certificate" )
184220
185221 return err
186222 }
187223
224+ log .Info ("renew run" )
225+
188226 return nil
189227}
190228
191229// renewCertificates periodically renews the SSL certificates.
192230func (cb * CertBot ) renewCertificates () {
193- fmt . Println ( "Starting SSL certificate renewal process" )
231+ log . Info ( "starting SSL certificate renewal process" )
194232
195233 ticker := time .NewTicker (24 * time .Hour )
196234 defer ticker .Stop ()
235+
197236 for range ticker .C {
198- fmt . Println ( "Checking if SSL certificate needs to be renewed" )
237+ log . Info ( "checking if SSL certificate needs to be renewed" )
199238 if err := cb .executeRenewCertificates (); err != nil {
200- log .Fatal ("Failed to renew SSL certificate" )
239+ log .Fatal ("failed to renew SSL certificate" )
201240 }
202241
203- fmt . Println ( "SSL certificate successfully renewed" )
204- cb .renewedCallback ()
242+ log . Info ( "ssl certificate successfully renewed" )
243+ cb .Config . RenewedCallback ()
205244 }
206245}
0 commit comments