|
6 | 6 | "encoding/json" |
7 | 7 | "errors" |
8 | 8 | "flag" |
9 | | - "errors" |
10 | 9 | "fmt" |
11 | 10 | "log" |
12 | 11 | "log/slog" |
|
86 | 85 | // Multi-workflow mode flags |
87 | 86 | databaseDSN = flag.String("database-dsn", "", "PostgreSQL connection string for multi-workflow mode") |
88 | 87 | jwtSecret = flag.String("jwt-secret", "", "JWT signing secret for API authentication") |
89 | | - adminEmail = flag.String("admin-email", "", "Initial admin user email (first-run bootstrap)") |
90 | | - adminPassword = flag.String("admin-password", "", "Initial admin user password (first-run bootstrap)") |
91 | | - multiWorkflowAddr = flag.String("multi-workflow-addr", ":8090", "HTTP listen address for multi-workflow REST API") |
| 88 | + adminEmail = flag.String("admin-email", "", "Initial admin user email (first-run bootstrap)") |
| 89 | + adminPassword = flag.String("admin-password", "", "Initial admin user password (first-run bootstrap)") |
92 | 90 |
|
93 | 91 | // License flags |
94 | 92 | licenseKey = flag.String("license-key", "", "License key for the workflow engine (or set WORKFLOW_LICENSE_KEY env var)") |
@@ -1402,114 +1400,14 @@ func main() { |
1402 | 1400 | })) |
1403 | 1401 |
|
1404 | 1402 | if *databaseDSN != "" { |
1405 | | - // Multi-workflow mode: connect to PostgreSQL, run migrations, start the |
1406 | | - // REST API router on a dedicated port. Single-config mode is skipped. |
1407 | | - if *jwtSecret == "" { |
1408 | | - log.Fatal("multi-workflow mode: --jwt-secret is required") |
1409 | | - } |
1410 | | - logger.Info("Starting in multi-workflow mode", |
1411 | | - "database_dsn_set", true, |
1412 | | - "admin_email_set", *adminEmail != "", |
1413 | | - "api_addr", *multiWorkflowAddr, |
1414 | | - ) |
1415 | | - dbCtx, dbCancel := context.WithTimeout(context.Background(), 30*time.Second) |
1416 | | - defer dbCancel() |
1417 | | - pgStore, pgErr := evstore.NewPGStore(dbCtx, evstore.PGConfig{URL: *databaseDSN}) |
1418 | | - if pgErr != nil { |
1419 | | - log.Fatalf("multi-workflow mode: failed to connect to PostgreSQL: %v", pgErr) //nolint:gocritic // exitAfterDefer: intentional, cleanup is best-effort |
1420 | | - } |
1421 | | - migrator := evstore.NewMigrator(pgStore.Pool()) |
1422 | | - if mErr := migrator.Migrate(dbCtx); mErr != nil { |
1423 | | - log.Fatalf("multi-workflow mode: database migration failed: %v", mErr) |
1424 | | - } |
1425 | | - logger.Info("multi-workflow mode: database migrations applied") |
1426 | | - |
1427 | | - // Bootstrap admin user on first run. |
1428 | | - if *adminEmail != "" && *adminPassword != "" { |
1429 | | - _, lookupErr := pgStore.Users().GetByEmail(context.Background(), *adminEmail) |
1430 | | - switch { |
1431 | | - case errors.Is(lookupErr, evstore.ErrNotFound): |
1432 | | - hash, hashErr := bcrypt.GenerateFromPassword([]byte(*adminPassword), bcrypt.DefaultCost) |
1433 | | - if hashErr != nil { |
1434 | | - log.Fatalf("multi-workflow mode: failed to hash admin password: %v", hashErr) |
1435 | | - } |
1436 | | - now := time.Now() |
1437 | | - adminUser := &evstore.User{ |
1438 | | - ID: uuid.New(), |
1439 | | - Email: *adminEmail, |
1440 | | - PasswordHash: string(hash), |
1441 | | - DisplayName: "Admin", |
1442 | | - Active: true, |
1443 | | - CreatedAt: now, |
1444 | | - UpdatedAt: now, |
1445 | | - } |
1446 | | - if createErr := pgStore.Users().Create(context.Background(), adminUser); createErr != nil { |
1447 | | - logger.Warn("multi-workflow mode: failed to create admin user (may already exist)", "error", createErr) |
1448 | | - } else { |
1449 | | - logger.Info("multi-workflow mode: created bootstrap admin user", "email", *adminEmail) |
1450 | | - } |
1451 | | - case lookupErr != nil: |
1452 | | - log.Fatalf("multi-workflow mode: failed to check for admin user: %v", lookupErr) |
1453 | | - default: |
1454 | | - logger.Info("multi-workflow mode: admin user already exists, skipping bootstrap", "email", *adminEmail) |
1455 | | - } |
| 1403 | + // Multi-workflow mode: delegates to runMultiWorkflow which connects to |
| 1404 | + // PostgreSQL, runs migrations, starts the REST API, and blocks until shutdown. |
| 1405 | + if err := runMultiWorkflow(logger); err != nil { |
| 1406 | + log.Fatalf("Multi-workflow error: %v", err) |
1456 | 1407 | } |
1457 | | - |
1458 | | - engineBuilder := func(cfg *config.WorkflowConfig, lg *slog.Logger) (*workflow.StdEngine, modular.Application, error) { |
1459 | | - eng, _, _, buildErr := buildEngine(cfg, lg) |
1460 | | - if buildErr != nil { |
1461 | | - return nil, nil, buildErr |
1462 | | - } |
1463 | | - app := eng.GetApp() |
1464 | | - return eng, app, nil |
1465 | | - } |
1466 | | - engineMgr := workflow.NewWorkflowEngineManager(pgStore.Workflows(), pgStore.CrossWorkflowLinks(), logger, engineBuilder) |
1467 | | - |
1468 | | - apiRouter := apihandler.NewRouter(apihandler.Stores{ |
1469 | | - Users: pgStore.Users(), |
1470 | | - Sessions: pgStore.Sessions(), |
1471 | | - Companies: pgStore.Companies(), |
1472 | | - Projects: pgStore.Projects(), |
1473 | | - Workflows: pgStore.Workflows(), |
1474 | | - Memberships: pgStore.Memberships(), |
1475 | | - Links: pgStore.CrossWorkflowLinks(), |
1476 | | - Executions: pgStore.Executions(), |
1477 | | - Logs: pgStore.Logs(), |
1478 | | - Audit: pgStore.Audit(), |
1479 | | - IAM: pgStore.IAM(), |
1480 | | - }, apihandler.Config{JWTSecret: *jwtSecret, Engine: engineMgr}) |
1481 | | - |
1482 | | - // Bind the listener eagerly so port conflicts are detected before the |
1483 | | - // deferred cleanup is registered (fail-fast instead of silent goroutine death). |
1484 | | - listener, listenErr := net.Listen("tcp", *multiWorkflowAddr) |
1485 | | - if listenErr != nil { |
1486 | | - log.Fatalf("multi-workflow mode: failed to listen on %s: %v", *multiWorkflowAddr, listenErr) |
1487 | | - } |
1488 | | - |
1489 | | - apiServer := &http.Server{ |
1490 | | - Handler: apiRouter, |
1491 | | - ReadHeaderTimeout: 10 * time.Second, |
1492 | | - } |
1493 | | - go func() { |
1494 | | - logger.Info("multi-workflow API listening", "addr", *multiWorkflowAddr) |
1495 | | - if sErr := apiServer.Serve(listener); sErr != nil && sErr != http.ErrServerClosed { |
1496 | | - logger.Error("multi-workflow API server error", "error", sErr) |
1497 | | - } |
1498 | | - }() |
1499 | | - defer func() { |
1500 | | - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) |
1501 | | - defer shutdownCancel() |
1502 | | - if sErr := apiServer.Shutdown(shutdownCtx); sErr != nil { |
1503 | | - logger.Warn("multi-workflow API server shutdown error", "error", sErr) |
1504 | | - } |
1505 | | - if sErr := engineMgr.StopAll(shutdownCtx); sErr != nil { |
1506 | | - logger.Warn("multi-workflow engine manager shutdown error", "error", sErr) |
1507 | | - } |
1508 | | - if shutdownCtx.Err() == context.DeadlineExceeded { |
1509 | | - logger.Warn("multi-workflow shutdown timed out; some in-flight operations may be incomplete") |
1510 | | - } |
1511 | | - pgStore.Close() |
1512 | | - }() |
| 1408 | + fmt.Println("Shutdown complete") |
| 1409 | + return |
| 1410 | + } |
1513 | 1411 |
|
1514 | 1412 | // Load configuration — supports both single-workflow and multi-workflow application configs. |
1515 | 1413 | cfg, appCfg, err := loadConfig(logger) |
@@ -1643,7 +1541,7 @@ func runMultiWorkflow(logger *slog.Logger) error { |
1643 | 1541 | apiRouter := apihandler.NewRouter(stores, apiCfg) |
1644 | 1542 |
|
1645 | 1543 | // 7. Set up admin UI and management infrastructure for workflow management |
1646 | | - singleCfg, err := loadConfig(logger) |
| 1544 | + singleCfg, _, err := loadConfig(logger) |
1647 | 1545 | if err != nil { |
1648 | 1546 | return fmt.Errorf("load config: %w", err) |
1649 | 1547 | } |
|
0 commit comments