178 lines
5.2 KiB
Go
178 lines
5.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"jiggablend/internal/auth"
|
|
"jiggablend/internal/config"
|
|
"jiggablend/internal/database"
|
|
"jiggablend/internal/logger"
|
|
manager "jiggablend/internal/manager"
|
|
"jiggablend/internal/storage"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var managerCmd = &cobra.Command{
|
|
Use: "manager",
|
|
Short: "Start the Jiggablend manager server",
|
|
Long: `Start the Jiggablend manager server to coordinate render jobs.`,
|
|
Run: runManager,
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(managerCmd)
|
|
|
|
// Flags with env binding via viper
|
|
managerCmd.Flags().StringP("port", "p", "8080", "Server port")
|
|
managerCmd.Flags().String("db", "jiggablend.db", "Database path")
|
|
managerCmd.Flags().String("storage", "./jiggablend-storage", "Storage path")
|
|
managerCmd.Flags().StringP("log-file", "l", "", "Log file path (truncated on start, if not set logs only to stdout)")
|
|
managerCmd.Flags().String("log-level", "info", "Log level (debug, info, warn, error)")
|
|
managerCmd.Flags().BoolP("verbose", "v", false, "Enable verbose logging (same as --log-level=debug)")
|
|
|
|
// Bind flags to viper with JIGGABLEND_ prefix
|
|
viper.SetEnvPrefix("JIGGABLEND")
|
|
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
|
viper.AutomaticEnv()
|
|
|
|
viper.BindPFlag("port", managerCmd.Flags().Lookup("port"))
|
|
viper.BindPFlag("db", managerCmd.Flags().Lookup("db"))
|
|
viper.BindPFlag("storage", managerCmd.Flags().Lookup("storage"))
|
|
viper.BindPFlag("log_file", managerCmd.Flags().Lookup("log-file"))
|
|
viper.BindPFlag("log_level", managerCmd.Flags().Lookup("log-level"))
|
|
viper.BindPFlag("verbose", managerCmd.Flags().Lookup("verbose"))
|
|
}
|
|
|
|
func runManager(cmd *cobra.Command, args []string) {
|
|
// Get config values (flags take precedence over env vars)
|
|
port := viper.GetString("port")
|
|
dbPath := viper.GetString("db")
|
|
storagePath := viper.GetString("storage")
|
|
logFile := viper.GetString("log_file")
|
|
logLevel := viper.GetString("log_level")
|
|
verbose := viper.GetBool("verbose")
|
|
|
|
// Initialize logger
|
|
if logFile != "" {
|
|
if err := logger.InitWithFile(logFile); err != nil {
|
|
logger.Fatalf("Failed to initialize logger: %v", err)
|
|
}
|
|
defer func() {
|
|
if l := logger.GetDefault(); l != nil {
|
|
l.Close()
|
|
}
|
|
}()
|
|
} else {
|
|
logger.InitStdout()
|
|
}
|
|
|
|
// Set log level
|
|
if verbose {
|
|
logger.SetLevel(logger.LevelDebug)
|
|
} else {
|
|
logger.SetLevel(logger.ParseLevel(logLevel))
|
|
}
|
|
|
|
if logFile != "" {
|
|
logger.Infof("Logging to file: %s", logFile)
|
|
}
|
|
logger.Debugf("Log level: %s", logLevel)
|
|
|
|
// Initialize database
|
|
db, err := database.NewDB(dbPath)
|
|
if err != nil {
|
|
logger.Fatalf("Failed to initialize database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Initialize config from database
|
|
cfg := config.NewConfig(db)
|
|
if err := cfg.InitializeFromEnv(); err != nil {
|
|
logger.Fatalf("Failed to initialize config: %v", err)
|
|
}
|
|
logger.Info("Configuration loaded from database")
|
|
|
|
// Initialize auth
|
|
authHandler, err := auth.NewAuth(db, cfg)
|
|
if err != nil {
|
|
logger.Fatalf("Failed to initialize auth: %v", err)
|
|
}
|
|
|
|
// Initialize storage
|
|
storageHandler, err := storage.NewStorage(storagePath)
|
|
if err != nil {
|
|
logger.Fatalf("Failed to initialize storage: %v", err)
|
|
}
|
|
|
|
// Check if Blender is available
|
|
if err := checkBlenderAvailable(); err != nil {
|
|
logger.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)
|
|
}
|
|
logger.Info("Blender is available")
|
|
|
|
// Check if ImageMagick is available
|
|
if err := checkImageMagickAvailable(); err != nil {
|
|
logger.Fatalf("ImageMagick is not available: %v\n"+
|
|
"The manager requires ImageMagick to be installed and in PATH for EXR preview conversion.\n"+
|
|
"Please install ImageMagick and ensure 'magick' or 'convert' command is accessible.", err)
|
|
}
|
|
logger.Info("ImageMagick is available")
|
|
|
|
// Create manager server
|
|
server, err := manager.NewManager(db, cfg, authHandler, storageHandler)
|
|
if err != nil {
|
|
logger.Fatalf("Failed to create server: %v", err)
|
|
}
|
|
|
|
// Start server
|
|
addr := fmt.Sprintf(":%s", port)
|
|
logger.Infof("Starting manager server on %s", addr)
|
|
logger.Infof("Database: %s", dbPath)
|
|
logger.Infof("Storage: %s", storagePath)
|
|
|
|
httpServer := &http.Server{
|
|
Addr: addr,
|
|
Handler: server,
|
|
MaxHeaderBytes: 1 << 20,
|
|
ReadTimeout: 0,
|
|
WriteTimeout: 0,
|
|
}
|
|
|
|
if err := httpServer.ListenAndServe(); err != nil {
|
|
logger.Fatalf("Server failed: %v", err)
|
|
}
|
|
}
|
|
|
|
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))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkImageMagickAvailable() error {
|
|
// Try 'magick' first (ImageMagick 7+)
|
|
cmd := exec.Command("magick", "--version")
|
|
output, err := cmd.CombinedOutput()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Fall back to 'convert' (ImageMagick 6 or legacy mode)
|
|
cmd = exec.Command("convert", "--version")
|
|
output, err = cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to run 'magick --version' or 'convert --version': %w (output: %s)", err, string(output))
|
|
}
|
|
return nil
|
|
}
|