Update .gitignore to include log files and database journal files. Modify go.mod to update dependencies for go-sqlite3 and cloud.google.com/go/compute/metadata. Enhance Makefile to include logging options for manager and runner commands. Introduce new job token handling in auth package and implement database migration scripts. Refactor manager and runner components to improve job processing and metadata extraction. Add support for video preview in frontend components and enhance WebSocket management for channel subscriptions.

This commit is contained in:
2026-01-02 13:55:19 -06:00
parent edc8ea160c
commit 94490237fe
44 changed files with 9463 additions and 7875 deletions

View File

@@ -6,11 +6,11 @@ import (
"os/exec"
"strings"
"jiggablend/internal/api"
"jiggablend/internal/auth"
"jiggablend/internal/config"
"jiggablend/internal/database"
"jiggablend/internal/logger"
manager "jiggablend/internal/manager"
"jiggablend/internal/storage"
"github.com/spf13/cobra"
@@ -117,8 +117,16 @@ func runManager(cmd *cobra.Command, args []string) {
}
logger.Info("Blender is available")
// Create API server
server, err := api.NewServer(db, cfg, authHandler, storageHandler)
// 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)
}
@@ -150,3 +158,20 @@ func checkBlenderAvailable() error {
}
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
}

View File

@@ -36,6 +36,7 @@ func init() {
runnerCmd.Flags().StringP("log-file", "l", "", "Log file path (truncated on start, if not set logs only to stdout)")
runnerCmd.Flags().String("log-level", "info", "Log level (debug, info, warn, error)")
runnerCmd.Flags().BoolP("verbose", "v", false, "Enable verbose logging (same as --log-level=debug)")
runnerCmd.Flags().Duration("poll-interval", 5*time.Second, "Job polling interval")
// Bind flags to viper with JIGGABLEND_ prefix
runnerViper.SetEnvPrefix("JIGGABLEND")
@@ -49,6 +50,7 @@ func init() {
runnerViper.BindPFlag("log_file", runnerCmd.Flags().Lookup("log-file"))
runnerViper.BindPFlag("log_level", runnerCmd.Flags().Lookup("log-level"))
runnerViper.BindPFlag("verbose", runnerCmd.Flags().Lookup("verbose"))
runnerViper.BindPFlag("poll_interval", runnerCmd.Flags().Lookup("poll-interval"))
}
func runRunner(cmd *cobra.Command, args []string) {
@@ -60,14 +62,15 @@ func runRunner(cmd *cobra.Command, args []string) {
logFile := runnerViper.GetString("log_file")
logLevel := runnerViper.GetString("log_level")
verbose := runnerViper.GetBool("verbose")
pollInterval := runnerViper.GetDuration("poll_interval")
var client *runner.Client
var r *runner.Runner
defer func() {
if r := recover(); r != nil {
logger.Errorf("Runner panicked: %v", r)
if client != nil {
client.CleanupWorkspace()
if rec := recover(); rec != nil {
logger.Errorf("Runner panicked: %v", rec)
if r != nil {
r.Cleanup()
}
os.Exit(1)
}
@@ -77,7 +80,7 @@ func runRunner(cmd *cobra.Command, args []string) {
hostname, _ = os.Hostname()
}
// Generate unique runner ID
// Generate unique runner ID suffix
runnerIDStr := generateShortID()
// Generate runner name with ID if not provided
@@ -114,23 +117,24 @@ func runRunner(cmd *cobra.Command, args []string) {
logger.Infof("Logging to file: %s", logFile)
}
client = runner.NewClient(managerURL, name, hostname)
// Create runner
r = runner.New(managerURL, name, hostname)
// Check for required tools early to fail fast
if err := r.CheckRequiredTools(); err != nil {
logger.Fatalf("Required tool check failed: %v", err)
}
// Clean up orphaned workspace directories
client.CleanupWorkspace()
r.Cleanup()
// Probe capabilities
// Probe capabilities and log them
logger.Debug("Probing runner capabilities...")
client.ProbeCapabilities()
capabilities := client.GetCapabilities()
capabilities := r.ProbeCapabilities()
capList := []string{}
for cap, value := range capabilities {
if enabled, ok := value.(bool); ok && enabled {
capList = append(capList, cap)
} else if count, ok := value.(int); ok && count > 0 {
capList = append(capList, fmt.Sprintf("%s=%d", cap, count))
} else if count, ok := value.(float64); ok && count > 0 {
capList = append(capList, fmt.Sprintf("%s=%.0f", cap, count))
}
}
if len(capList) > 0 {
@@ -154,7 +158,7 @@ func runRunner(cmd *cobra.Command, args []string) {
for {
var err error
runnerID, _, _, err = client.Register(apiKey)
runnerID, err = r.Register(apiKey)
if err == nil {
logger.Infof("Registered runner with ID: %d", runnerID)
break
@@ -178,14 +182,6 @@ func runRunner(cmd *cobra.Command, args []string) {
}
}
// Start WebSocket connection
go client.ConnectWebSocketWithReconnect()
// Start heartbeat loop
go client.HeartbeatLoop()
logger.Info("Runner started, connecting to manager via WebSocket...")
// Signal handlers
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
@@ -193,13 +189,14 @@ func runRunner(cmd *cobra.Command, args []string) {
go func() {
sig := <-sigChan
logger.Infof("Received signal: %v, killing all processes and cleaning up...", sig)
client.KillAllProcesses()
client.CleanupWorkspace()
r.KillAllProcesses()
r.Cleanup()
os.Exit(0)
}()
// Block forever
select {}
// Start polling for jobs
logger.Infof("Runner started, polling for jobs (interval: %v)...", pollInterval)
r.Start(pollInterval)
}
func generateShortID() string {