package main import ( "context" "flag" "fmt" "log" "net" "net/http" "os" "os/signal" "strings" "syscall" "time" "finance-duck/internal/app" "finance-duck/internal/server" frontend "finance-duck/web" ) func main() { if err := run(); err != nil { log.Print(err) os.Exit(1) } } func run() error { dir := flag.String("data", "./finance", "canonical finance directory") listen := flag.String("listen", "127.0.0.1:8080", "HTTP listen address; use a VPN address for remote access") publicURL := flag.String("public-url", "", "exact browser origin required for non-localhost access (e.g. https://finance.internal)") rebuild := flag.Bool("rebuild", false, "rebuild disposable DuckDB cache then exit") flag.Parse() host, _, err := net.SplitHostPort(*listen) if err != nil { return fmt.Errorf("listen: %w", err) } if (host == "" || host == "0.0.0.0" || host == "::") && *publicURL == "" { return fmt.Errorf("wildcard binding requires -public-url and VPN/firewall isolation; there is no login") } a, err := app.Open(*dir) if err != nil { return err } defer a.Close() ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() if *rebuild { s, e := a.Rebuild(ctx) if e != nil { return e } if s.Status.IndexError != "" { return fmt.Errorf("index: %s", s.Status.IndexError) } fmt.Println("DuckDB rebuilt from canonical plaintext") return nil } assets, err := frontend.Assets() if err != nil { return err } handler, err := server.New(a, assets, strings.TrimRight(*publicURL, "/")) if err != nil { return err } srv := &http.Server{Addr: *listen, Handler: handler, ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 90 * time.Second, MaxHeaderBytes: 32 << 10, BaseContext: func(net.Listener) context.Context { return ctx }} ln, err := net.Listen("tcp", *listen) if err != nil { return err } schedulerDone := make(chan struct{}) go func() { defer close(schedulerDone); a.RunScheduler(ctx) }() stopped := make(chan struct{}) go func() { <-ctx.Done() shutdown, c := context.WithTimeout(context.Background(), 15*time.Second) defer c() srv.Shutdown(shutdown) close(stopped) }() log.Printf("Finance Duck listening on http://%s — no login; keep behind your VPN", *listen) err = srv.Serve(ln) cancel() <-stopped <-schedulerDone if err == http.ErrServerClosed { return nil } return err }