149 lines
4.6 KiB
Go
149 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"filefast/backend/internal/config"
|
|
"filefast/backend/internal/handler"
|
|
"filefast/backend/internal/scheduler"
|
|
"filefast/backend/internal/service"
|
|
"filefast/backend/internal/storage"
|
|
"filefast/backend/internal/store"
|
|
"filefast/backend/internal/ws"
|
|
)
|
|
|
|
func main() {
|
|
cfg := config.Load()
|
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.LogLevel}))
|
|
memStore := store.NewMemoryStore(cfg.Runtime)
|
|
var redisBackplane *storage.RedisClient
|
|
storageConnected := false
|
|
redisConnected := false
|
|
|
|
sqliteClient, err := storage.NewSQLiteClient(cfg.SQLite)
|
|
if err != nil {
|
|
logger.Error("failed to initialize sqlite client", "path", cfg.SQLite.Path, "error", err)
|
|
os.Exit(1)
|
|
}
|
|
defer sqliteClient.Close()
|
|
|
|
storagePingCtx, storagePingCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer storagePingCancel()
|
|
if err := sqliteClient.Ping(storagePingCtx); err != nil {
|
|
logger.Warn("sqlite connection failed", "path", cfg.SQLite.Path, "error", err)
|
|
} else {
|
|
storageConnected = true
|
|
logger.Info("sqlite connected", "path", cfg.SQLite.Path)
|
|
|
|
bootstrapCtx, bootstrapCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer bootstrapCancel()
|
|
|
|
if err := sqliteClient.EnsureSchema(bootstrapCtx); err != nil {
|
|
logger.Warn("failed to ensure sqlite schema", "error", err)
|
|
} else {
|
|
if err := sqliteClient.EnsureAdminUser(bootstrapCtx, cfg.Admin.Username, cfg.Admin.Password); err != nil {
|
|
logger.Warn("failed to ensure admin user", "username", cfg.Admin.Username, "error", err)
|
|
}
|
|
|
|
if err := sqliteClient.ResetOnlineDevices(bootstrapCtx); err != nil {
|
|
logger.Warn("failed to reset device online states", "error", err)
|
|
}
|
|
|
|
snapshot, err := sqliteClient.LoadSnapshot(bootstrapCtx, cfg.Runtime)
|
|
if err != nil {
|
|
logger.Warn("failed to restore state from sqlite", "error", err)
|
|
} else {
|
|
memStore.LoadSnapshot(snapshot)
|
|
logger.Info(
|
|
"restored state from sqlite",
|
|
"devices", len(snapshot.Devices),
|
|
"rooms", len(snapshot.Rooms),
|
|
"transfers", len(snapshot.Transfers),
|
|
"fallback_objects", len(snapshot.FallbackObjects),
|
|
)
|
|
}
|
|
}
|
|
|
|
memStore.SetPersistence(sqliteClient, 5*time.Second, func(kind, id string, err error) {
|
|
logger.Warn("failed to persist state", "kind", kind, "id", id, "error", err)
|
|
})
|
|
}
|
|
|
|
redisClient := storage.NewRedisClient(cfg.Redis)
|
|
defer redisClient.Close()
|
|
|
|
redisPingCtx, redisPingCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer redisPingCancel()
|
|
if err := redisClient.Ping(redisPingCtx); err != nil {
|
|
logger.Warn("redis connection failed", "addr", cfg.Redis.Addr, "db", cfg.Redis.DB, "error", err)
|
|
} else {
|
|
redisConnected = true
|
|
logger.Info("redis connected", "addr", cfg.Redis.Addr, "db", cfg.Redis.DB)
|
|
redisBackplane = redisClient
|
|
}
|
|
|
|
deviceService := service.NewDeviceService(memStore, redisBackplane, redisBackplane)
|
|
roomService := service.NewRoomService(memStore, cfg.RoomTTL)
|
|
transferService := service.NewTransferService(memStore)
|
|
adminService := service.NewAdminService(memStore, cfg.Admin, redisBackplane, sqliteClient)
|
|
|
|
minioClient, err := storage.NewMinIOClient(cfg.MinIO)
|
|
if err != nil {
|
|
logger.Error("failed to initialize minio client", "error", err)
|
|
}
|
|
|
|
hub := ws.NewHub(logger, deviceService, redisBackplane)
|
|
go hub.Run()
|
|
|
|
cleanupScheduler := scheduler.NewCleanupScheduler(logger, memStore, minioClient)
|
|
cleanupScheduler.Start()
|
|
defer cleanupScheduler.Stop()
|
|
|
|
httpHandler := handler.NewHTTPHandler(handler.Dependencies{
|
|
Config: cfg,
|
|
Logger: logger,
|
|
Store: memStore,
|
|
DeviceService: deviceService,
|
|
RoomService: roomService,
|
|
TransferService: transferService,
|
|
AdminService: adminService,
|
|
MinIOClient: minioClient,
|
|
Hub: hub,
|
|
StorageReady: storageConnected,
|
|
RedisReady: redisConnected,
|
|
})
|
|
|
|
server := &http.Server{
|
|
Addr: cfg.HTTPAddress,
|
|
Handler: httpHandler.Router(),
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
}
|
|
|
|
go func() {
|
|
logger.Info("server listening", "addr", cfg.HTTPAddress)
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
logger.Error("server crashed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
stop := make(chan os.Signal, 1)
|
|
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
|
|
<-stop
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
logger.Error("server shutdown failed", "error", err)
|
|
}
|
|
|
|
logger.Info("server stopped")
|
|
}
|