mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 09:37:02 +00:00
165 lines
4.5 KiB
Go
165 lines
4.5 KiB
Go
// main is the inbucket daemon launcher
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"expvar"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/jhillyerd/inbucket/pkg/config"
|
|
"github.com/jhillyerd/inbucket/pkg/log"
|
|
"github.com/jhillyerd/inbucket/pkg/message"
|
|
"github.com/jhillyerd/inbucket/pkg/msghub"
|
|
"github.com/jhillyerd/inbucket/pkg/policy"
|
|
"github.com/jhillyerd/inbucket/pkg/rest"
|
|
"github.com/jhillyerd/inbucket/pkg/server/pop3"
|
|
"github.com/jhillyerd/inbucket/pkg/server/smtp"
|
|
"github.com/jhillyerd/inbucket/pkg/server/web"
|
|
"github.com/jhillyerd/inbucket/pkg/storage"
|
|
"github.com/jhillyerd/inbucket/pkg/storage/file"
|
|
"github.com/jhillyerd/inbucket/pkg/webui"
|
|
)
|
|
|
|
var (
|
|
// version contains the build version number, populated during linking.
|
|
version = "undefined"
|
|
|
|
// date contains the build date, populated during linking.
|
|
date = "undefined"
|
|
)
|
|
|
|
func init() {
|
|
// Server uptime for status page.
|
|
startTime := time.Now()
|
|
expvar.Publish("uptime", expvar.Func(func() interface{} {
|
|
return time.Since(startTime) / time.Second
|
|
}))
|
|
|
|
// Goroutine count for status page.
|
|
expvar.Publish("goroutines", expvar.Func(func() interface{} {
|
|
return runtime.NumGoroutine()
|
|
}))
|
|
}
|
|
|
|
func main() {
|
|
// Command line flags.
|
|
help := flag.Bool("help", false, "Displays help on flags and env variables.")
|
|
pidfile := flag.String("pidfile", "", "Write our PID into the specified file.")
|
|
logfile := flag.String("logfile", "stderr", "Write out log into the specified file.")
|
|
flag.Usage = func() {
|
|
fmt.Fprintln(os.Stderr, "Usage: inbucket [options]")
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.Parse()
|
|
if *help {
|
|
flag.Usage()
|
|
fmt.Fprintln(os.Stderr, "")
|
|
config.Usage()
|
|
return
|
|
}
|
|
// Process configuration.
|
|
config.Version = version
|
|
config.BuildDate = date
|
|
conf, err := config.Process()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
// Setup signal handler.
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
|
|
// Initialize logging.
|
|
log.SetLogLevel(conf.LogLevel)
|
|
if err := log.Initialize(*logfile); err != nil {
|
|
fmt.Fprintf(os.Stderr, "%v", err)
|
|
os.Exit(1)
|
|
}
|
|
defer log.Close()
|
|
log.Infof("Inbucket %v (%v) starting...", config.Version, config.BuildDate)
|
|
// Write pidfile if requested.
|
|
if *pidfile != "" {
|
|
pidf, err := os.Create(*pidfile)
|
|
if err != nil {
|
|
log.Errorf("Failed to create %q: %v", *pidfile, err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Fprintf(pidf, "%v\n", os.Getpid())
|
|
if err := pidf.Close(); err != nil {
|
|
log.Errorf("Failed to close PID file %q: %v", *pidfile, err)
|
|
}
|
|
}
|
|
// Configure internal services.
|
|
rootCtx, rootCancel := context.WithCancel(context.Background())
|
|
shutdownChan := make(chan bool)
|
|
msgHub := msghub.New(rootCtx, conf.Web.MonitorHistory)
|
|
store := file.New(conf.Storage)
|
|
addrPolicy := &policy.Addressing{Config: conf.SMTP}
|
|
mmanager := &message.StoreManager{Store: store, Hub: msgHub}
|
|
// Start Retention scanner.
|
|
retentionScanner := storage.NewRetentionScanner(conf.Storage, store, shutdownChan)
|
|
retentionScanner.Start()
|
|
// Start HTTP server.
|
|
web.Initialize(conf, shutdownChan, mmanager, msgHub)
|
|
webui.SetupRoutes(web.Router)
|
|
rest.SetupRoutes(web.Router)
|
|
go web.Start(rootCtx)
|
|
// Start POP3 server.
|
|
pop3Server := pop3.New(conf.POP3, shutdownChan, store)
|
|
go pop3Server.Start(rootCtx)
|
|
// Start SMTP server.
|
|
smtpServer := smtp.NewServer(conf.SMTP, shutdownChan, mmanager, addrPolicy)
|
|
go smtpServer.Start(rootCtx)
|
|
// Loop forever waiting for signals or shutdown channel.
|
|
signalLoop:
|
|
for {
|
|
select {
|
|
case sig := <-sigChan:
|
|
switch sig {
|
|
case syscall.SIGHUP:
|
|
log.Infof("Recieved SIGHUP, cycling logfile")
|
|
log.Rotate()
|
|
case syscall.SIGINT:
|
|
// Shutdown requested
|
|
log.Infof("Received SIGINT, shutting down")
|
|
close(shutdownChan)
|
|
case syscall.SIGTERM:
|
|
// Shutdown requested
|
|
log.Infof("Received SIGTERM, shutting down")
|
|
close(shutdownChan)
|
|
}
|
|
case <-shutdownChan:
|
|
rootCancel()
|
|
break signalLoop
|
|
}
|
|
}
|
|
// Wait for active connections to finish.
|
|
go timedExit(*pidfile)
|
|
smtpServer.Drain()
|
|
pop3Server.Drain()
|
|
retentionScanner.Join()
|
|
removePIDFile(*pidfile)
|
|
}
|
|
|
|
// removePIDFile removes the PID file if created.
|
|
func removePIDFile(pidfile string) {
|
|
if pidfile != "" {
|
|
if err := os.Remove(pidfile); err != nil {
|
|
log.Errorf("Failed to remove %q: %v", pidfile, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// timedExit is called as a goroutine during shutdown, it will force an exit after 15 seconds.
|
|
func timedExit(pidfile string) {
|
|
time.Sleep(15 * time.Second)
|
|
log.Errorf("Clean shutdown took too long, forcing exit")
|
|
removePIDFile(pidfile)
|
|
os.Exit(0)
|
|
}
|