mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-13 00:38:49 +00:00
- Introduced a new `Domain` field in the gateway configuration to support HTTPS and ACME certificate provisioning. - Implemented domain validation to ensure proper format. - Enhanced the main gateway logic to handle ACME challenges and manage TLS certificates using CertMagic. - Updated installation script to create necessary directories for ACME certificate storage and configure firewall rules for HTTP/HTTPS ports.
242 lines
7.6 KiB
Go
242 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/gateway"
|
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
|
"github.com/caddyserver/certmagic"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const acmeEmail = "dev@debros.io"
|
|
|
|
func setupLogger() *logging.ColoredLogger {
|
|
logger, err := logging.NewColoredLogger(logging.ComponentGeneral, true)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return logger
|
|
}
|
|
|
|
func main() {
|
|
logger := setupLogger()
|
|
|
|
// Load gateway config (flags/env)
|
|
cfg := parseGatewayConfig(logger)
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Starting gateway initialization...")
|
|
|
|
// Initialize gateway (connect client, prepare routes)
|
|
gw, err := gateway.New(logger, cfg)
|
|
if err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "failed to initialize gateway", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
defer gw.Close()
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Gateway initialization completed successfully")
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Creating HTTP server and routes...")
|
|
|
|
// Wrap handler with host enforcement if domain is set
|
|
handler := gw.Routes()
|
|
if cfg.Domain != "" {
|
|
d := cfg.Domain
|
|
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
host := r.Host
|
|
if i := strings.IndexByte(host, ':'); i >= 0 {
|
|
host = host[:i]
|
|
}
|
|
if !strings.EqualFold(host, d) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
gw.Routes().ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// If domain is configured, use ACME TLS on :443 and :80 for challenges
|
|
if cfg.Domain != "" {
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Production ACME TLS enabled",
|
|
zap.String("domain", cfg.Domain),
|
|
zap.String("acme_email", acmeEmail),
|
|
)
|
|
|
|
// Setup CertMagic with file storage
|
|
certDir := filepath.Join(os.ExpandEnv("$HOME"), ".debros", "certmagic")
|
|
if home, err := os.UserHomeDir(); err == nil {
|
|
certDir = filepath.Join(home, ".debros", "certmagic")
|
|
}
|
|
|
|
if err := os.MkdirAll(certDir, 0700); err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "failed to create certmagic directory", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Configure CertMagic for ACME
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Provisioning ACME certificate...",
|
|
zap.String("domain", cfg.Domain),
|
|
)
|
|
|
|
// Use the default CertMagic instance and configure storage
|
|
certmagic.Default.Storage = &certmagic.FileStorage{Path: certDir}
|
|
|
|
// Setup ACME issuer
|
|
acmeIssuer := certmagic.ACMEIssuer{
|
|
CA: certmagic.LetsEncryptProductionCA,
|
|
Email: acmeEmail,
|
|
Agreed: true,
|
|
}
|
|
certmagic.Default.Issuers = []certmagic.Issuer{&acmeIssuer}
|
|
|
|
// Manage the domain
|
|
if err := certmagic.ManageSync(context.Background(), []string{cfg.Domain}); err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "ACME ManageSync failed", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Get TLS config
|
|
tlsCfg := certmagic.Default.TLSConfig()
|
|
|
|
// Start HTTP server on :80 for ACME challenges and redirect to HTTPS
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Starting HTTP server on :80 for ACME challenges")
|
|
go func() {
|
|
httpMux := http.NewServeMux()
|
|
httpMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
// Redirect all HTTP to HTTPS
|
|
u := *r.URL
|
|
u.Scheme = "https"
|
|
u.Host = cfg.Domain
|
|
http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
|
|
})
|
|
// HTTP server for ACME challenges and redirects
|
|
if err := http.ListenAndServe(":80", httpMux); err != nil && err != http.ErrServerClosed {
|
|
logger.ComponentError(logging.ComponentGeneral, "HTTP :80 server error", zap.Error(err))
|
|
}
|
|
logger.ComponentInfo(logging.ComponentGeneral, "HTTP :80 server stopped")
|
|
}()
|
|
|
|
// Start HTTPS server on :443
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Starting HTTPS server on :443 with ACME certificate",
|
|
zap.String("domain", cfg.Domain),
|
|
)
|
|
|
|
httpsServer := &http.Server{
|
|
Addr: ":443",
|
|
Handler: handler,
|
|
TLSConfig: tlsCfg,
|
|
}
|
|
|
|
ln, err := net.Listen("tcp", httpsServer.Addr)
|
|
if err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "failed to bind HTTPS listen address", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
logger.ComponentInfo(logging.ComponentGeneral, "HTTPS listener bound", zap.String("listen_addr", ln.Addr().String()))
|
|
|
|
// Serve in a goroutine so we can handle graceful shutdown on signals.
|
|
serveErrCh := make(chan error, 1)
|
|
go func() {
|
|
if err := httpsServer.ServeTLS(ln, "", ""); err != nil && err != http.ErrServerClosed {
|
|
serveErrCh <- err
|
|
return
|
|
}
|
|
serveErrCh <- nil
|
|
}()
|
|
|
|
// Wait for termination signal or server error
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
|
|
|
select {
|
|
case sig := <-quit:
|
|
logger.ComponentInfo(logging.ComponentGeneral, "shutdown signal received", zap.String("signal", sig.String()))
|
|
case err := <-serveErrCh:
|
|
if err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "HTTPS server error", zap.Error(err))
|
|
} else {
|
|
logger.ComponentInfo(logging.ComponentGeneral, "HTTPS server exited normally")
|
|
}
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Shutting down gateway HTTPS server...")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
if err := httpsServer.Shutdown(ctx); err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "HTTPS server shutdown error", zap.Error(err))
|
|
} else {
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Gateway shutdown complete")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Fallback: HTTP server on configured listen_addr when no domain
|
|
server := &http.Server{
|
|
Addr: cfg.ListenAddr,
|
|
Handler: handler,
|
|
}
|
|
|
|
// Try to bind listener explicitly so binding failures are visible immediately.
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Gateway HTTP server starting",
|
|
zap.String("addr", cfg.ListenAddr),
|
|
zap.String("namespace", cfg.ClientNamespace),
|
|
zap.Int("bootstrap_peer_count", len(cfg.BootstrapPeers)),
|
|
)
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Attempting to bind HTTP listener...")
|
|
|
|
ln, err := net.Listen("tcp", cfg.ListenAddr)
|
|
if err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "failed to bind HTTP listen address", zap.Error(err))
|
|
// exit because server cannot function without a listener
|
|
os.Exit(1)
|
|
}
|
|
logger.ComponentInfo(logging.ComponentGeneral, "HTTP listener bound", zap.String("listen_addr", ln.Addr().String()))
|
|
|
|
// Serve in a goroutine so we can handle graceful shutdown on signals.
|
|
serveErrCh := make(chan error, 1)
|
|
go func() {
|
|
if err := server.Serve(ln); err != nil && err != http.ErrServerClosed {
|
|
serveErrCh <- err
|
|
return
|
|
}
|
|
serveErrCh <- nil
|
|
}()
|
|
|
|
// Wait for termination signal or server error
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
|
|
|
select {
|
|
case sig := <-quit:
|
|
logger.ComponentInfo(logging.ComponentGeneral, "shutdown signal received", zap.String("signal", sig.String()))
|
|
case err := <-serveErrCh:
|
|
if err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "HTTP server error", zap.Error(err))
|
|
// continue to shutdown path so we close resources cleanly
|
|
} else {
|
|
logger.ComponentInfo(logging.ComponentGeneral, "HTTP server exited normally")
|
|
}
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Shutting down gateway HTTP server...")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
logger.ComponentError(logging.ComponentGeneral, "HTTP server shutdown error", zap.Error(err))
|
|
} else {
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Gateway shutdown complete")
|
|
}
|
|
}
|