82 lines
2.0 KiB
Go
82 lines
2.0 KiB
Go
|
|
// Package main is the CarrotAssistant backend entrypoint.
|
||
|
|
//
|
||
|
|
// It loads configuration, opens the database, bootstraps an initial admin
|
||
|
|
// account if none exists, and serves HTTP until interrupted.
|
||
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"log"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"os/signal"
|
||
|
|
"syscall"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"code.littlelan.cn/CarrotAssistant/backend/internal/config"
|
||
|
|
"code.littlelan.cn/CarrotAssistant/backend/internal/security"
|
||
|
|
"code.littlelan.cn/CarrotAssistant/backend/internal/server"
|
||
|
|
"code.littlelan.cn/CarrotAssistant/backend/internal/store"
|
||
|
|
)
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
cfg, err := config.Load()
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("load config: %v", err)
|
||
|
|
}
|
||
|
|
log.Printf("data dir: %s", cfg.DataDir)
|
||
|
|
|
||
|
|
db, err := store.Open(cfg.DBPath)
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("open db: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
crypto, err := security.NewCrypto(cfg.MasterKey)
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("init crypto: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Bootstrap an admin account on first run so the console is reachable.
|
||
|
|
// Production should set ADMIN_BOOTSTRAP_USER / ADMIN_BOOTSTRAP_PASSWORD.
|
||
|
|
if cfg.AdminBootstrapUser != "" {
|
||
|
|
if err := server.EnsureAdmin(db, cfg.AdminBootstrapUser, cfg.AdminBootstrapPassword); err != nil {
|
||
|
|
log.Printf("ensure admin: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
deps := server.Deps{
|
||
|
|
DB: db,
|
||
|
|
Config: cfg,
|
||
|
|
Crypto: crypto,
|
||
|
|
SkillsDir: cfg.SkillsDir,
|
||
|
|
}
|
||
|
|
|
||
|
|
handler := server.New(deps)
|
||
|
|
|
||
|
|
srv := &http.Server{
|
||
|
|
Addr: cfg.HTTPAddr,
|
||
|
|
Handler: handler,
|
||
|
|
ReadHeaderTimeout: 10 * time.Second,
|
||
|
|
}
|
||
|
|
|
||
|
|
go func() {
|
||
|
|
log.Printf("listening on %s", cfg.HTTPAddr)
|
||
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||
|
|
log.Fatalf("server: %v", err)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
// Graceful shutdown on SIGINT / SIGTERM.
|
||
|
|
stop := make(chan os.Signal, 1)
|
||
|
|
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
|
||
|
|
<-stop
|
||
|
|
log.Println("shutting down")
|
||
|
|
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||
|
|
defer cancel()
|
||
|
|
if err := srv.Shutdown(ctx); err != nil {
|
||
|
|
fmt.Fprintf(os.Stderr, "shutdown: %v\n", err)
|
||
|
|
}
|
||
|
|
}
|