Skip to content

Commit

Permalink
refactor(project): restructure project layout for improved modularity
Browse files Browse the repository at this point in the history
  • Loading branch information
yarlson committed Jan 26, 2025
1 parent 979cb59 commit 27c3020
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 54 deletions.
1 change: 1 addition & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ builds:
- env:
- CGO_ENABLED=0
binary: zero
main: ./cmd/zero
goos:
- linux
- darwin
Expand Down
25 changes: 12 additions & 13 deletions main.go → cmd/zero/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@ import (
"context"
"errors"
"fmt"
"github.com/yarlson/zero/internal/acme"
"github.com/yarlson/zero/internal/cert"
"github.com/yarlson/zero/internal/server"
"github.com/yarlson/zero/internal/task"
"log"
"os"
"os/signal"
"syscall"
"time"

"github.com/spf13/pflag"

"github.com/yarlson/zero/certificates"
"github.com/yarlson/zero/cron"
"github.com/yarlson/zero/server"
"github.com/yarlson/zero/zerossl"
)

const (
Expand Down Expand Up @@ -54,7 +53,7 @@ func parseFlags() (*Config, error) {
return nil, errors.New("domain and email are required")
}

if _, err := cron.ParseTime(cfg.Time); err != nil {
if _, err := task.ParseTime(cfg.Time); err != nil {
return nil, fmt.Errorf("invalid time format: %w", err)
}

Expand All @@ -72,11 +71,11 @@ func main() {
}

// Setup services
zeroSSLService := zerossl.New()
certService := certificates.New(zeroSSLService)
zeroSSL := acme.NewZeroSSL()
certManager := cert.NewStore(zeroSSL)

// Start HTTP server
srv := server.New(certService, cfg.Port)
srv := server.New(certManager, cfg.Port)
go func() {
if err := srv.Start(); err != nil {
log.Fatalf("HTTP server error: %v", err)
Expand All @@ -85,18 +84,18 @@ func main() {

// Start certificate checker
checkCert := func(ctx context.Context) error {
return certService.CheckCertificate(ctx, cfg.Domain, cfg.Email, cfg.CertDir)
return certManager.CheckCertificate(ctx, cfg.Domain, cfg.Email, cfg.CertDir)
}

cronService := cron.New(checkCert, cfg.Time)
go cronService.Start()
scheduler := task.NewScheduler(checkCert, cfg.Time)
go scheduler.Start()

// Wait for shutdown signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan

log.Println("Shutting down...")
cronService.Stop()
scheduler.Stop()
time.Sleep(time.Second)
}
24 changes: 6 additions & 18 deletions zerossl/zerossl.go → internal/acme/zerossl.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package zerossl
package acme

import (
"bytes"
Expand All @@ -25,31 +25,19 @@ const (
zeroSSLURL = "https://acme.zerossl.com/v2/DV90"
)

type Service struct {
type ZeroSSL struct {
client *http.Client
}

type Option func(*Service)

func WithClient(client *http.Client) Option {
return func(s *Service) {
s.client = client
}
}

func New(options ...Option) *Service {
service := &Service{
func NewZeroSSL() *ZeroSSL {
service := &ZeroSSL{
client: &http.Client{Timeout: 10 * time.Second},
}

for _, option := range options {
option(service)
}

return service
}

func (s *Service) FetchCredentials(ctx context.Context, email string) (kid, hmacKey string, err error) {
func (s *ZeroSSL) FetchCredentials(ctx context.Context, email string) (kid, hmacKey string, err error) {
data := []byte(fmt.Sprintf("email=%s", email))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, zeroSSLEABAPIURL, bytes.NewBuffer(data))
if err != nil {
Expand Down Expand Up @@ -87,7 +75,7 @@ func (s *Service) FetchCredentials(ctx context.Context, email string) (kid, hmac
return result.EABKID, result.EABHMACKey, nil
}

func (s *Service) ObtainCertificate(ctx context.Context, domain, email string, challengeHandler func(token, response string)) ([][]byte, crypto.PrivateKey, error) {
func (s *ZeroSSL) ObtainCertificate(ctx context.Context, domain, email string, challengeHandler func(token, response string)) ([][]byte, crypto.PrivateKey, error) {
eabKID, eabHMACKey, err := s.FetchCredentials(ctx, email)
if err != nil {
return nil, nil, fmt.Errorf("fetch ZeroSSL credentials: %w", err)
Expand Down
30 changes: 15 additions & 15 deletions certificates/certificates.go → internal/cert/store.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package certificates
package cert

import (
"context"
Expand All @@ -18,24 +18,24 @@ const (
renewBeforeDays = 30
)

type ZeroSSLService interface {
type ZeroSSL interface {
ObtainCertificate(ctx context.Context, domain, email string, challengeHandler func(token, response string)) ([][]byte, crypto.PrivateKey, error)
}

type Service struct {
zeroSSLService ZeroSSLService
type Store struct {
zeroSSLService ZeroSSL
challenges map[string]string
challengeMu sync.RWMutex
}

func New(zeroSSLService ZeroSSLService) *Service {
return &Service{
zeroSSLService: zeroSSLService,
func NewStore(zeroSSL ZeroSSL) *Store {
return &Store{
zeroSSLService: zeroSSL,
challenges: make(map[string]string),
}
}

func (s *Service) saveCertificate(filename string, certBytes [][]byte) error {
func (s *Store) saveCertificate(filename string, certBytes [][]byte) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("create certificate file: %w", err)
Expand All @@ -50,7 +50,7 @@ func (s *Service) saveCertificate(filename string, certBytes [][]byte) error {
return nil
}

func (s *Service) savePrivateKey(filename string, privateKey crypto.PrivateKey) error {
func (s *Store) savePrivateKey(filename string, privateKey crypto.PrivateKey) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("create private key file: %w", err)
Expand All @@ -64,7 +64,7 @@ func (s *Service) savePrivateKey(filename string, privateKey crypto.PrivateKey)
return pem.Encode(file, &pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes})
}

func (s *Service) LoadCertificate(filename string) (*x509.Certificate, error) {
func (s *Store) LoadCertificate(filename string) (*x509.Certificate, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("read certificate file: %w", err)
Expand All @@ -83,11 +83,11 @@ func (s *Service) LoadCertificate(filename string) (*x509.Certificate, error) {
return nil, fmt.Errorf("no certificate found in %s", filename)
}

func (s *Service) CertificateNeedsRenewal(cert *x509.Certificate) bool {
func (s *Store) CertificateNeedsRenewal(cert *x509.Certificate) bool {
return time.Now().Add(renewBeforeDays * 24 * time.Hour).After(cert.NotAfter)
}

func (s *Service) ObtainOrRenewCertificate(ctx context.Context, domain, email, certFile, keyFile string) error {
func (s *Store) ObtainOrRenewCertificate(ctx context.Context, domain, email, certFile, keyFile string) error {
certs, privateKey, err := s.zeroSSLService.ObtainCertificate(ctx, domain, email, s.StoreChallenge)
if err != nil {
return fmt.Errorf("obtain certificate: %w", err)
Expand All @@ -105,20 +105,20 @@ func (s *Service) ObtainOrRenewCertificate(ctx context.Context, domain, email, c
return nil
}

func (s *Service) StoreChallenge(token, response string) {
func (s *Store) StoreChallenge(token, response string) {
s.challengeMu.Lock()
defer s.challengeMu.Unlock()
s.challenges[token] = response
}

func (s *Service) GetChallengeResponse(token string) (string, bool) {
func (s *Store) GetChallengeResponse(token string) (string, bool) {
s.challengeMu.RLock()
defer s.challengeMu.RUnlock()
response, exists := s.challenges[token]
return response, exists
}

func (s *Service) CheckCertificate(ctx context.Context, domain, email, certDir string) error {
func (s *Store) CheckCertificate(ctx context.Context, domain, email, certDir string) error {
certFile := filepath.Join(certDir, domain+".crt")
keyFile := filepath.Join(certDir, domain+".key")

Expand Down
File renamed without changes.
16 changes: 8 additions & 8 deletions cron/cron.go → internal/task/scheduler.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cron
package task

import (
"context"
Expand All @@ -12,18 +12,18 @@ type Runner interface {
Run() error
}

// Service handles the scheduling and execution of periodic tasks
type Service struct {
// Scheduler handles the scheduling and execution of periodic tasks
type Scheduler struct {
task func(context.Context) error
time string
ctx context.Context
cancel context.CancelFunc
}

// New creates a new cron Service
func New(task func(context.Context) error, time string) *Service {
// NewScheduler creates a new cron Scheduler
func NewScheduler(task func(context.Context) error, time string) *Scheduler {
ctx, cancel := context.WithCancel(context.Background())
return &Service{
return &Scheduler{
task: task,
time: time,
ctx: ctx,
Expand All @@ -32,7 +32,7 @@ func New(task func(context.Context) error, time string) *Service {
}

// Start begins the cron service
func (s *Service) Start() {
func (s *Scheduler) Start() {
renewalTime, err := ParseTime(s.time)
if err != nil {
log.Fatalf("Invalid time format: %v", err)
Expand Down Expand Up @@ -64,7 +64,7 @@ func (s *Service) Start() {
}

// Stop gracefully stops the cron service
func (s *Service) Stop() {
func (s *Scheduler) Stop() {
s.cancel()
}

Expand Down

0 comments on commit 27c3020

Please sign in to comment.