2026-04-17 11:56:39 +07:00

244 lines
7.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"fmt"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
"acme-reverseproxy/config"
"acme-reverseproxy/nfs"
"acme-reverseproxy/proxymap"
"github.com/BurntSushi/toml"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/crypto/acme/autocert"
)
var cfg config.Config
func main() {
app := cli.NewApp()
app.Name = "acme-reverseproxy"
app.Usage = "A TLS-serving reverse-proxy, with the certificates generated from LetsEncrypt"
app.Authors = []cli.Author{{Name: "Forc", Email: "redirsvr@mail.ru"}}
app.Flags = []cli.Flag{cli.BoolFlag{Name: "debug,D", Usage: "debug output"}}
app.Commands = []cli.Command{
{
Name: "gen",
Description: "generators of sorts",
Subcommands: []cli.Command{
{
Name: "config",
Description: "generate a sample mapping configuration",
Action: GenConfigAction,
},
},
},
{
Name: "srv",
Description: "Start the reverseproxy server",
Action: SrvCommand,
Flags: []cli.Flag{
cli.StringFlag{
Name: "config",
Value: filepath.Join(os.Getenv("HOME"), ".acme-reverseproxy.toml"),
Usage: "Configuration of mapping of hostname -> listener",
},
},
Before: BeforeAction,
},
}
sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
app.Run(os.Args)
}
func BeforeAction(c *cli.Context) error {
if c.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
logrus.Info("Debug mode enabled")
} else {
logrus.SetLevel(logrus.InfoLevel)
}
logrus.Infof("Loading config from: %s", c.String("config"))
buf, err := os.ReadFile(c.String("config"))
if err != nil {
if os.IsNotExist(err) {
logrus.Errorf("No config file found at %q. Try 'gen config'", c.String("config"))
}
return err
}
tmpConfig := config.Config{}
if err := toml.Unmarshal(buf, &tmpConfig); err != nil {
logrus.Errorf("Failed to parse config: %v", err)
return err
}
cfg = tmpConfig
logrus.Infof("Config loaded: CA.Email=%s, CA.CacheDir=%s, WellKnownDir=%s",
cfg.CA.Email, cfg.CA.CacheDir, cfg.WellKnownDir)
logrus.Infof("Configured mappings: %d domains", len(cfg.Mapping))
for host, target := range cfg.Mapping {
logrus.Infof(" %s -> %s", host, target)
}
return nil
}
func GenConfigAction(c *cli.Context) error {
tmpConfig := config.Config{
CA: config.CA{
Email: "admin@p42.ru",
CacheDir: "/tmp/acme-reverseproxy",
},
Mapping: map[string]string{
"p42.ru": "http://localhost:5000",
},
WellKnownDir: "/tmp/.well-known",
NFS: config.NFSConfig{
Enabled: false,
Server: "192.168.1.100",
ExportPath: "/export/acme",
MountPoint: "/mnt/acme-wellknown",
Options: "rw,vers=4.1,timeo=50,retrans=2",
},
}
e := toml.NewEncoder(os.Stdout)
if err := e.Encode(tmpConfig); err != nil {
return err
}
return nil
}
func SrvCommand(c *cli.Context) error {
// Инициализируем NFS менеджер
nfs.Init(nfs.Config{
Enabled: cfg.NFS.Enabled,
Server: cfg.NFS.Server,
ExportPath: cfg.NFS.ExportPath,
MountPoint: cfg.NFS.MountPoint,
Options: cfg.NFS.Options,
})
// Если NFS включен, монтируем его при старте
if cfg.NFS.Enabled {
logrus.Info("Initializing NFS mount at startup...")
if err := nfs.EnsureMounted(); err != nil {
logrus.Warnf("NFS mount failed at startup (will retry on demand): %v", err)
} else {
logrus.Info("NFS mounted successfully at startup")
}
}
list := []string{}
for key := range cfg.Mapping {
if key != "" {
list = append(list, key)
}
}
if len(list) == 0 {
logrus.Error("No domains configured in Mapping section")
return cli.NewExitError("No domains configured", 2)
}
logrus.Infof("Initializing reverse proxy for %d domains: %s", len(list), strings.Join(list, ", "))
rpm, err := proxymap.ToReverseProxyMap(cfg.Mapping)
if err != nil {
logrus.Errorf("Failed to create reverse proxy map: %v", err)
return cli.NewExitError(err, 2)
}
wellKnownDir := cfg.WellKnownDir
if wellKnownDir == "" {
wellKnownDir = "/tmp/.well-known"
}
// Если NFS включен и указан MountPoint, используем его для .well-known
if cfg.NFS.Enabled && cfg.NFS.MountPoint != "" {
wellKnownDir = cfg.NFS.MountPoint
logrus.Infof("Using NFS mount point for .well-known: %s", wellKnownDir)
}
logrus.Infof("Well-known directory: %s", wellKnownDir)
rph := proxymap.NewReverseProxiesHandlerWithWellKnown(rpm, wellKnownDir)
logrus.Infof("ACME whitelist: %s", strings.Join(list, ", "))
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(list...),
}
if cfg.CA.Email != "" {
m.Email = cfg.CA.Email
logrus.Infof("ACME email: %s", cfg.CA.Email)
}
if cfg.CA.CacheDir != "" {
m.Cache = autocert.DirCache(cfg.CA.CacheDir)
logrus.Infof("ACME cache directory: %s", cfg.CA.CacheDir)
}
httpsListener := m.Listener()
httpsServer := &http.Server{
Handler: rph,
}
httpHandler := m.HTTPHandler(rph)
httpServer := &http.Server{Addr: ":80", Handler: httpHandler}
logrus.Info(strings.Repeat("=", 61))
logrus.Info("Starting reverse proxy servers...")
logrus.Info(strings.Repeat("=", 61))
var wg sync.WaitGroup
var firstError error
var mu sync.Mutex
wg.Add(2)
go func() {
defer wg.Done()
logrus.Info("Starting HTTPS server on port 443...")
if err := httpsServer.Serve(httpsListener); err != nil {
mu.Lock()
if firstError == nil {
firstError = err
}
mu.Unlock()
logrus.Errorf("HTTPS server stopped with error: %v", err)
} else {
logrus.Info("HTTPS server stopped normally")
}
}()
go func() {
defer wg.Done()
logrus.Info("Starting HTTP server on port 80 (redirecting to HTTPS)...")
if err := httpServer.ListenAndServe(); err != nil {
mu.Lock()
if firstError == nil {
firstError = err
}
mu.Unlock()
logrus.Errorf("HTTP server stopped with error: %v", err)
} else {
logrus.Info("HTTP server stopped normally")
}
}()
time.Sleep(100 * time.Millisecond)
logrus.Info("Servers started successfully. Waiting for requests...")
logrus.Info("Press Ctrl+C to stop")
wg.Wait()
// При остановке размонтируем NFS если он был смонтирован
if cfg.NFS.Enabled {
if err := nfs.DefaultManager.Unmount(); err != nil {
logrus.Warnf("Failed to unmount NFS on shutdown: %v", err)
}
}
if firstError != nil {
logrus.Errorf("Server exited with error: %v", firstError)
return cli.NewExitError(firstError, 2)
}
logrus.Info("All servers stopped")
return nil
}
func MakeDir(path string) string {
err := os.MkdirAll(path, 0777)
if err != nil {
fmt.Println(err, path)
return path
}
return path
}