main.go raw
1 // orly-certs is a certificate management service that obtains and renews
2 // wildcard SSL certificates from Let's Encrypt using DNS-01 challenges.
3 //
4 // It supports multiple DNS providers via the lego library and stores
5 // certificates at a conventional file path for web apps to consume.
6 //
7 // Configuration is via environment variables:
8 // - ORLY_CERTS_DOMAIN: Wildcard domain (e.g., "*.myapp.com")
9 // - ORLY_CERTS_EMAIL: Email for Let's Encrypt account
10 // - ORLY_CERTS_DNS_PROVIDER: DNS provider name (cloudflare, route53, etc.)
11 // - ORLY_CERTS_OUTPUT_DIR: Certificate output directory (default: /var/cache/orly-certs)
12 //
13 // Provider-specific credentials are set via standard lego environment variables.
14 // See https://go-acme.github.io/lego/dns/ for documentation.
15 package main
16
17 import (
18 "context"
19 "fmt"
20 "os"
21 "os/signal"
22 "syscall"
23 "time"
24
25 "next.orly.dev/pkg/lol"
26 "next.orly.dev/pkg/lol/chk"
27 "next.orly.dev/pkg/lol/log"
28 )
29
30 func main() {
31 cfg := loadConfig()
32 lol.SetLogLevel(cfg.LogLevel)
33
34 log.I.F("orly-certs starting")
35 log.I.F(" domain: %s", cfg.Domain)
36 log.I.F(" email: %s", cfg.Email)
37 log.I.F(" dns provider: %s", cfg.DNSProvider)
38 log.I.F(" output dir: %s", cfg.OutputDir)
39 log.I.F(" acme server: %s", cfg.ACMEServerURL())
40
41 ctx, cancel := context.WithCancel(context.Background())
42 defer cancel()
43
44 // Set up signal handling
45 sigs := make(chan os.Signal, 1)
46 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
47
48 go func() {
49 <-sigs
50 log.I.F("shutdown signal received")
51 cancel()
52 }()
53
54 // Create certificate manager
55 manager, err := NewCertManager(cfg)
56 if chk.E(err) {
57 log.F.F("failed to create certificate manager: %v", err)
58 }
59
60 // Initial certificate check/obtain
61 if err := manager.EnsureCertificate(); chk.E(err) {
62 log.F.F("failed to ensure certificate: %v", err)
63 }
64
65 // Start renewal loop
66 log.I.F("starting renewal check loop (interval: %s)", cfg.CheckInterval)
67 ticker := time.NewTicker(cfg.CheckInterval)
68 defer ticker.Stop()
69
70 for {
71 select {
72 case <-ticker.C:
73 if err := manager.CheckRenewal(); chk.E(err) {
74 log.E.F("renewal check failed: %v", err)
75 }
76 case <-ctx.Done():
77 log.I.F("orly-certs shutting down")
78 return
79 }
80 }
81 }
82
83 func usage() {
84 fmt.Fprintf(os.Stderr, `orly-certs - DNS-01 wildcard certificate manager
85
86 Usage: orly-certs [options]
87
88 Environment Variables:
89 ORLY_CERTS_DOMAIN Wildcard domain (e.g., *.myapp.com) [required]
90 ORLY_CERTS_EMAIL Email for Let's Encrypt account [required]
91 ORLY_CERTS_DNS_PROVIDER DNS provider name [required]
92 ORLY_CERTS_OUTPUT_DIR Certificate output directory [default: /var/cache/orly-certs]
93 ORLY_CERTS_RENEW_DAYS Renew when expiring within N days [default: 30]
94 ORLY_CERTS_CHECK_INTERVAL Renewal check interval [default: 12h]
95 ORLY_CERTS_ACME_SERVER ACME server URL [default: production Let's Encrypt]
96 ORLY_CERTS_LOG_LEVEL Log level [default: info]
97
98 Supported DNS Providers:
99 cloudflare, route53, hetzner, digitalocean, google, namecheap, godaddy,
100 ovh, vultr, linode, gandi, dnsimple, duckdns, azure, alidns, and 80+ more.
101
102 Provider credentials are set via standard lego environment variables.
103 See https://go-acme.github.io/lego/dns/ for documentation.
104
105 Example:
106 export CF_API_TOKEN="your-cloudflare-api-token"
107 export ORLY_CERTS_DOMAIN="*.myapp.com"
108 export ORLY_CERTS_EMAIL="admin@myapp.com"
109 export ORLY_CERTS_DNS_PROVIDER="cloudflare"
110 ./orly-certs
111 `)
112 }
113