129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/coreos/go-systemd/v22/daemon"
|
|
cfg "github.com/grmrgecko/repo-sync/config"
|
|
"github.com/grmrgecko/repo-sync/server"
|
|
"github.com/grmrgecko/repo-sync/state"
|
|
"github.com/kardianos/service"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// stopChan carries a shutdown request from the service manager. It is
|
|
// buffered so a stop delivered after the signal loop exits cannot block the
|
|
// service supervisor.
|
|
var stopChan = make(chan struct{}, 1)
|
|
|
|
// ServerCmd runs the caching mirror server.
|
|
type ServerCmd struct {
|
|
Bind string `name:"http.bind" help:"Override the configured bind address." optional:""`
|
|
Port uint `name:"http.port" help:"Override the configured port." optional:""`
|
|
}
|
|
|
|
// updateCFG applies the command line overrides onto the active
|
|
// configuration. The overridden fields are read only when the listener is
|
|
// built, which happens on this goroutine, so a reload can amend them in
|
|
// place after Init has published the new configuration.
|
|
func (c *ServerCmd) updateCFG() {
|
|
if c.Bind != "" {
|
|
cfg.C.HTTP.BindAddr = c.Bind
|
|
}
|
|
if c.Port != 0 {
|
|
cfg.C.HTTP.Port = c.Port
|
|
}
|
|
}
|
|
|
|
// Run starts the mirror server: configuration, state, crawl loops, and the
|
|
// HTTP listener, then handles signals until shutdown.
|
|
func (c *ServerCmd) Run() error {
|
|
// main installed the configuration; the server additionally needs the
|
|
// domains and mounts the sync commands do without.
|
|
c.updateCFG()
|
|
if err := cfg.C.RequireServer(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Load persisted crawl state.
|
|
if err := state.Load(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Start the background loops and the listener.
|
|
loopCtx, loopCancel := context.WithCancel(context.Background())
|
|
defer loopCancel()
|
|
go state.S.FlushLoop(loopCtx)
|
|
crawlDone := make(chan struct{})
|
|
go func() {
|
|
defer close(crawlDone)
|
|
server.CrawlLoop(loopCtx)
|
|
}()
|
|
|
|
// stopCrawls cancels the loops and waits for in-flight crawls so their
|
|
// outcomes are recorded before the final state flush. The crawl loop
|
|
// must return first: a scheduler pass in flight can still dispatch
|
|
// crawls, and a WaitGroup Add racing Wait is a misuse panic.
|
|
stopCrawls := func() {
|
|
loopCancel()
|
|
<-crawlDone
|
|
server.WaitCrawls()
|
|
}
|
|
|
|
httpServer := server.NewServer()
|
|
if err := httpServer.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Attach to the service manager when not run interactively, so a stop
|
|
// request arrives on stopChan, then report readiness now that the
|
|
// listener is bound.
|
|
if !service.Interactive() {
|
|
svc, err := new(ServiceCmd).service()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go svc.Run()
|
|
}
|
|
_, _ = daemon.SdNotify(false, daemon.SdNotifyReady)
|
|
|
|
// Handle signals: reload on SIGHUP, shut down on SIGINT/SIGTERM or on a
|
|
// stop from the service manager.
|
|
signals := make(chan os.Signal, 1)
|
|
signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
|
|
SigLoop:
|
|
for {
|
|
select {
|
|
case <-stopChan:
|
|
break SigLoop
|
|
case sig := <-signals:
|
|
if sig != syscall.SIGHUP {
|
|
break SigLoop
|
|
}
|
|
log.Info("Reloading configuration.")
|
|
if err := cfg.InitServer(flags.ConfigPath); err != nil {
|
|
log.WithError(err).Error("Unable to reload configuration; keeping previous values.")
|
|
continue
|
|
}
|
|
applyGlobalFlags()
|
|
c.updateCFG()
|
|
if err := httpServer.Reload(); err != nil {
|
|
// Exiting on a failed rebind must still flush crawl state.
|
|
stopCrawls()
|
|
if saveErr := state.S.Save(); saveErr != nil {
|
|
log.WithError(saveErr).Error("Failed to save state while shutting down.")
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shut down: stop the listener, stop the loops, then flush state.
|
|
log.Info("Shutting down.")
|
|
httpServer.Shutdown()
|
|
stopCrawls()
|
|
return state.S.Save()
|
|
}
|