package main import ( "flag" "fmt" "log" "net/http" "os" "os/exec" "jiggablend/internal/api" "jiggablend/internal/auth" "jiggablend/internal/database" "jiggablend/internal/logger" "jiggablend/internal/storage" ) func main() { var ( port = flag.String("port", getEnv("PORT", "8080"), "Server port") dbPath = flag.String("db", getEnv("DB_PATH", "jiggablend.db"), "Database path") storagePath = flag.String("storage", getEnv("STORAGE_PATH", "./jiggablend-storage"), "Storage path") logDir = flag.String("log-dir", getEnv("LOG_DIR", "./logs"), "Log directory") logMaxSize = flag.Int("log-max-size", getEnvInt("LOG_MAX_SIZE", 100), "Maximum log file size in MB before rotation") logMaxBackups = flag.Int("log-max-backups", getEnvInt("LOG_MAX_BACKUPS", 5), "Maximum number of rotated log files to keep") logMaxAge = flag.Int("log-max-age", getEnvInt("LOG_MAX_AGE", 30), "Maximum age in days for rotated log files") ) flag.Parse() // Initialize logger (writes to both stdout and log file with rotation) logDirPath := *logDir if err := logger.Init(logDirPath, "manager.log", *logMaxSize, *logMaxBackups, *logMaxAge); err != nil { log.Fatalf("Failed to initialize logger: %v", err) } defer func() { if l := logger.GetDefault(); l != nil { l.Close() } }() log.Printf("Log rotation configured: max_size=%dMB, max_backups=%d, max_age=%d days", *logMaxSize, *logMaxBackups, *logMaxAge) // Initialize database db, err := database.NewDB(*dbPath) if err != nil { log.Fatalf("Failed to initialize database: %v", err) } defer db.Close() // Initialize auth authHandler, err := auth.NewAuth(db.DB) if err != nil { log.Fatalf("Failed to initialize auth: %v", err) } // Initialize storage storageHandler, err := storage.NewStorage(*storagePath) if err != nil { log.Fatalf("Failed to initialize storage: %v", err) } // Check if Blender is available (required for metadata extraction) if err := checkBlenderAvailable(); err != nil { log.Fatalf("Blender is not available: %v\n"+ "The manager requires Blender to be installed and in PATH for metadata extraction.\n"+ "Please install Blender and ensure it's accessible via the 'blender' command.", err) } log.Printf("Blender is available") // Create API server server, err := api.NewServer(db, authHandler, storageHandler) if err != nil { log.Fatalf("Failed to create server: %v", err) } // Start server with increased request body size limit for large file uploads addr := fmt.Sprintf(":%s", *port) log.Printf("Starting manager server on %s", addr) log.Printf("Database: %s", *dbPath) log.Printf("Storage: %s", *storagePath) httpServer := &http.Server{ Addr: addr, Handler: server, MaxHeaderBytes: 1 << 20, // 1 MB for headers ReadTimeout: 0, // No read timeout (for large uploads) WriteTimeout: 0, // No write timeout (for large uploads) } // Note: MaxRequestBodySize is not directly configurable in http.Server // It's handled by ParseMultipartForm in handlers, which we've already configured // But we need to ensure the server can handle large requests // The default limit is 10MB, but we bypass it by using ParseMultipartForm with larger limit if err := httpServer.ListenAndServe(); err != nil { log.Fatalf("Server failed: %v", err) } } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func getEnvInt(key string, defaultValue int) int { if value := os.Getenv(key); value != "" { var result int if _, err := fmt.Sscanf(value, "%d", &result); err == nil { return result } } return defaultValue } // checkBlenderAvailable checks if Blender is available by running `blender --version` func checkBlenderAvailable() error { cmd := exec.Command("blender", "--version") output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to run 'blender --version': %w (output: %s)", err, string(output)) } // If we got here, Blender is available return nil }