// Package main provides a sigsum-log-secondary binary package main import ( "context" "flag" "fmt" "net/http" "os" "os/signal" "sync" "syscall" "time" "github.com/google/trillian" "github.com/prometheus/client_golang/prometheus/promhttp" "google.golang.org/grpc" "git.sigsum.org/log-go/internal/db" "git.sigsum.org/log-go/internal/node/secondary" "git.sigsum.org/log-go/internal/utils" "git.sigsum.org/sigsum-go/pkg/client" "git.sigsum.org/sigsum-go/pkg/log" ) var ( externalEndpoint = flag.String("external-endpoint", "localhost:6965", "host:port specification of where sigsum-log-secondary serves clients") internalEndpoint = flag.String("internal-endpoint", "localhost:6967", "host:port specification of where sigsum-log-secondary serves other log nodes") rpcBackend = flag.String("trillian-rpc-server", "localhost:6962", "host:port specification of where Trillian serves clients") prefix = flag.String("url-prefix", "", "a prefix that proceeds /sigsum/v0/") trillianID = flag.Int64("tree-id", 0, "log identifier in the Trillian database") deadline = flag.Duration("deadline", time.Second*10, "deadline for backend requests") key = flag.String("key", "", "path to file with hex-encoded Ed25519 private key") interval = flag.Duration("interval", time.Second*30, "interval used to rotate the node's STH") testMode = flag.Bool("test-mode", false, "run in test mode (Default: false)") logFile = flag.String("log-file", "", "file to write logs to (Default: stderr)") logLevel = flag.String("log-level", "info", "log level (Available options: debug, info, warning, error. Default: info)") logColor = flag.Bool("log-color", false, "colored logging output (Default: false)") primaryURL = flag.String("primary-url", "", "primary node endpoint for fetching leaves") primaryPubkey = flag.String("primary-pubkey", "", "hex-encoded Ed25519 public key for primary node") gitCommit = "unknown" ) func main() { flag.Parse() if err := utils.SetupLogging(*logFile, *logLevel, *logColor); err != nil { log.Fatal("setup logging: %v", err) } log.Info("log-go git-commit %s", gitCommit) // wait for clean-up before exit var wg sync.WaitGroup defer wg.Wait() ctx, cancel := context.WithCancel(context.Background()) defer cancel() log.Debug("configuring log-go-secondary") node, err := setupSecondaryFromFlags() if err != nil { log.Fatal("setup secondary: %v", err) } log.Debug("starting periodic routine") go func() { wg.Add(1) defer wg.Done() node.Run(ctx) log.Debug("periodic routine shutdown") cancel() // must have periodic running }() server := &http.Server{Addr: *externalEndpoint, Handler: node.PublicHTTPMux} intserver := &http.Server{Addr: *internalEndpoint, Handler: node.InternalHTTPMux} log.Debug("starting await routine") go await(ctx, func() { wg.Add(1) defer wg.Done() ctxInner, _ := context.WithTimeout(ctx, time.Second*60) log.Info("stopping http server, please wait...") server.Shutdown(ctxInner) log.Info("... done") log.Info("stopping internal api server, please wait...") intserver.Shutdown(ctxInner) log.Info("... done") log.Info("stopping go routines, please wait...") cancel() log.Info("... done") }) go func() { wg.Add(1) defer wg.Done() log.Info("serving log nodes on %v/%v", *internalEndpoint, *prefix) if err = intserver.ListenAndServe(); err != http.ErrServerClosed { log.Error("serve(intserver): %v", err) } log.Debug("internal endpoints server shut down") cancel() }() log.Info("serving clients on %v/%v", *externalEndpoint, *prefix) if err = server.ListenAndServe(); err != http.ErrServerClosed { log.Error("serve(server): %v", err) } } // setupSecondaryFromFlags() sets up a new sigsum secondary node from flags. func setupSecondaryFromFlags() (*secondary.Secondary, error) { var s secondary.Secondary var err error // Setup logging configuration. s.Signer, s.Config.LogID, err = utils.NewLogIdentity(*key) if err != nil { return nil, fmt.Errorf("newLogIdentity: %v", err) } s.Config.TreeID = *trillianID s.Config.Prefix = *prefix s.Config.Deadline = *deadline s.Config.Interval = *interval // Setup trillian client. dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(s.Config.Deadline)} conn, err := grpc.Dial(*rpcBackend, dialOpts...) if err != nil { return nil, fmt.Errorf("Dial: %v", err) } s.TrillianClient = &db.TrillianClient{ TreeID: s.TreeID, GRPC: trillian.NewTrillianLogClient(conn), } // Setup primary node configuration. pubkey, err := utils.PubkeyFromHexString(*primaryPubkey) if err != nil { return nil, fmt.Errorf("invalid primary node pubkey: %v", err) } s.Primary = client.New(client.Config{ LogURL: *primaryURL, LogPub: *pubkey, }) // TODO: verify that GRPC.TreeType() == PREORDERED_LOG. // Register HTTP endpoints. mux := http.NewServeMux() s.PublicHTTPMux = mux // No external endpoints but we want to return 404. mux = http.NewServeMux() for _, h := range s.InternalHTTPHandlers() { log.Debug("adding internal handler: %s", h.Path()) mux.Handle(h.Path(), h) } s.InternalHTTPMux = mux log.Debug("adding prometheus handler to internal mux, on path: /metrics") http.Handle("/metrics", promhttp.Handler()) return &s, nil } // await waits for a shutdown signal and then runs a clean-up function func await(ctx context.Context, done func()) { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) select { case <-sigs: case <-ctx.Done(): } log.Debug("received shutdown signal") done() }