| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- // Command rekopub-web runs the converter behind the form on
- // rekopub.monoexp.com. It listens on 127.0.0.1 only, behind nginx, and runs
- // the rekopub command for each conversion.
- //
- // Settings come from the environment:
- //
- // PORT port to listen on (required)
- // REKOPUB the rekopub command (default: rekopub next to this program)
- // DATA_DIR writable folder; books are kept in DATA_DIR/books (default: data)
- // KEEP_HOURS hours a book is kept and its link works (default: 24)
- // LIMIT conversions for all visitors together... (default: 50)
- // LIMIT_PER_VISITOR ...and per visitor... (default: 10)
- // LIMIT_MINUTES ...within this many minutes (default: 15)
- // MAX_RUNNING conversions running at once (default: 2)
- // MAX_STORAGE_MB disk used by kept books before conversions pause (default: 2048)
- package main
- import (
- "context"
- "errors"
- "fmt"
- "log"
- "net/http"
- "os"
- "os/signal"
- "path/filepath"
- "strconv"
- "syscall"
- "time"
- "git.monoexp.com/bajdev/rekopub/internal/web"
- )
- func main() {
- log.SetFlags(0) // the journal adds timestamps
- port, cfg, err := config()
- if err != nil {
- log.Fatal(err)
- }
- srv, err := web.New(cfg)
- if err != nil {
- log.Fatal(err)
- }
- ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
- defer stop()
- go srv.CleanEvery(ctx, 10*time.Minute)
- hs := &http.Server{
- Addr: "127.0.0.1:" + port,
- Handler: srv.Handler(),
- ReadHeaderTimeout: 10 * time.Second,
- ReadTimeout: 30 * time.Second,
- IdleTimeout: 60 * time.Second,
- // No WriteTimeout: a conversion can take minutes. Each run of the
- // command has its own time limit instead.
- }
- go func() {
- <-ctx.Done()
- shutdown, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
- hs.Shutdown(shutdown)
- }()
- log.Printf("rekopub-web listening on %s, running %s", hs.Addr, cfg.CLI)
- if err := hs.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
- log.Fatal(err)
- }
- }
- func config() (string, web.Config, error) {
- var cfg web.Config
- port := os.Getenv("PORT")
- if _, err := strconv.ParseUint(port, 10, 16); err != nil {
- return "", cfg, fmt.Errorf("PORT %q: must be a port number", port)
- }
- cfg.CLI = os.Getenv("REKOPUB")
- if cfg.CLI == "" {
- exe, err := os.Executable()
- if err != nil {
- return "", cfg, err
- }
- cfg.CLI = filepath.Join(filepath.Dir(exe), "rekopub")
- }
- if _, err := os.Stat(cfg.CLI); err != nil {
- return "", cfg, fmt.Errorf("REKOPUB: %w", err)
- }
- data := os.Getenv("DATA_DIR")
- if data == "" {
- data = "data"
- }
- cfg.Dir = filepath.Join(data, "books")
- var err error
- num := func(name string) int {
- v := os.Getenv(name)
- if v == "" || err != nil {
- return 0 // the default
- }
- n, perr := strconv.Atoi(v)
- if perr != nil || n <= 0 {
- err = fmt.Errorf("%s %q: must be a whole number above 0", name, v)
- }
- return n
- }
- cfg.Keep = time.Duration(num("KEEP_HOURS")) * time.Hour
- cfg.Limit = num("LIMIT")
- cfg.PerVisitor = num("LIMIT_PER_VISITOR")
- cfg.Window = time.Duration(num("LIMIT_MINUTES")) * time.Minute
- cfg.MaxRunning = num("MAX_RUNNING")
- cfg.MaxStorage = int64(num("MAX_STORAGE_MB")) << 20
- return port, cfg, err
- }
|