1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"os/signal"
	"rss-kakapo/dal"
	"rss-kakapo/logic"
	"rss-kakapo/server"
	"rss-kakapo/shared"
	"rss-kakapo/texts"
	"syscall"
	"time"
)

// shutdownTimeout is how long open requests get to finish once the service is
// asked to stop.
const shutdownTimeout = 15 * time.Second

func main() {

	if len(os.Args) > 1 {
		os.Exit(runCli(os.Args[1:], os.Stdout, os.Stderr))
	}

	cfg := shared.LoadConfig()
	logger := initLogger(cfg)

	// The database is migrated before anything else uses it: the feed
	// follower and the messenger start their loops as soon as they exist.
	repo := dal.NewRepo(cfg, logger)
	repo.InitUpdateDb()

	userAgent := shared.NewUserAgent(cfg)
	prof := logic.NewProfiler(cfg, logger)
	txt := texts.NewTexts()
	keyStore := logic.NewKeyStore(cfg, repo)
	blockedFeeds := logic.NewBlockedFeeds(repo)
	sender := logic.NewActivitySender(cfg, logger, userAgent)
	userRetriever := logic.NewUserRetriever(cfg, userAgent, keyStore)
	sigChecker := logic.NewHttpSigChecker(logger, userRetriever)
	messenger := logic.NewMessenger(cfg, logger, repo, keyStore, sender)
	feedFollower := logic.NewFeedFollower(cfg, logger, userAgent, repo, blockedFeeds,
		messenger, txt, keyStore)
	userDir := logic.NewUserDirectory(cfg, logger, repo, keyStore, sender, txt)
	inbox := logic.NewInbox(cfg, logger, repo, txt, userDir, keyStore,
		sender, messenger, feedFollower)

	groups := []server.IHandlerGroup{
		server.NewApubHandlerGroup(cfg, logger, sender, sigChecker, userDir, inbox),
		server.NewApiHandlerGroup(cfg, logger, feedFollower, repo, prof),
		server.NewWebHandlerGroup(cfg, logger, repo, txt),
	}
	srv := server.NewHTTPServer(cfg, server.NewMux(groups, logger))

	logger.Printf("Application starting up")

	listener, err := net.Listen("tcp", srv.Addr)
	if err != nil {
		fmt.Fprintf(os.Stderr, "rss-kakapo: %v\n", err)
		os.Exit(1)
	}
	logger.Printf("Starting HTTP server at %v", srv.Addr)
	go srv.Serve(listener)

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	<-ctx.Done()
	stop()

	logger.Printf("Shutting down HTTP server")
	shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
	defer cancel()
	if err := srv.Shutdown(shutdownCtx); err != nil {
		logger.Errorf("Shutting down HTTP server: %v", err)
	}
	logger.Printf("Application shutting down")
}

// initLogger builds the process logger. Logs always go to stderr, which under
// systemd is captured by the journal. The log_file config field is ignored.
func initLogger(cfg *shared.Config) *shared.Logger {
	return shared.NewLogger(os.Stderr, cfg.LogLevel)
}