Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .cursorrules
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ Docs are located in /docs

The frontend is located in /web

The backend is spread across /api, /browser, and /cmd. When the user requests something, make sure you know if they're asking to update the docs, frontend, or backend.
The backend is spread across /api, /browser, and /cmd. When the user requests something, make sure you know if they're asking to update the docs, frontend, or backend.

Whenever you plan to use Terraform examples, use OpenTofu instead.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ jobs:
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Run docs tests
run: task test:docs
Expand Down
469 changes: 400 additions & 69 deletions api/exec.go

Large diffs are not rendered by default.

33 changes: 28 additions & 5 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

// setupCommonRoutes sets up the common routes for both server modes
func setupCommonRoutes(r *gin.Engine, runbookPath string, outputPath string, registry *ExecutableRegistry, useExecutableRegistry bool) {
func setupCommonRoutes(r *gin.Engine, runbookPath string, outputPath string, registry *ExecutableRegistry, sessionManager *SessionManager, useExecutableRegistry bool) {
// Get embedded filesystems for serving static assets
distFS, err := web.GetDistFS()
if err != nil {
Expand Down Expand Up @@ -51,8 +51,22 @@ func setupCommonRoutes(r *gin.Engine, runbookPath string, outputPath string, reg
// API endpoint to get registered executables
r.GET("/api/runbook/executables", HandleExecutablesRequest(registry))

// Session management endpoints (single session per runbook server)
// Public session endpoints (no auth required)
r.POST("/api/session", HandleCreateSession(sessionManager, runbookPath))
r.POST("/api/session/join", HandleJoinSession(sessionManager))

// Protected session endpoints (require Bearer token)
sessionAuth := r.Group("/api/session")
sessionAuth.Use(SessionAuthMiddleware(sessionManager))
{
sessionAuth.GET("", HandleGetSession(sessionManager))
sessionAuth.POST("/reset", HandleResetSession(sessionManager))
sessionAuth.DELETE("", HandleDeleteSession(sessionManager))
}

// API endpoint to execute check scripts
r.POST("/api/exec", HandleExecRequest(registry, runbookPath, useExecutableRegistry, outputPath))
r.POST("/api/exec", HandleExecRequest(registry, runbookPath, useExecutableRegistry, outputPath, sessionManager))

// API endpoints for managing generated files
r.GET("/api/generated-files/check", HandleGeneratedFilesCheck(outputPath))
Expand Down Expand Up @@ -95,6 +109,9 @@ func StartServer(runbookPath string, port int, outputPath string) error {
return fmt.Errorf("failed to create executable registry: %w", err)
}

// Create session manager for persistent environment
sessionManager := NewSessionManager()

// Use release mode for end-users (quieter logs, better performance)
// Use gin.New() instead of gin.Default() to skip the default logger middleware
// This keeps the logs clean for end-users while still including recovery middleware
Expand All @@ -109,7 +126,7 @@ func StartServer(runbookPath string, port int, outputPath string) error {
r.GET("/api/runbook", HandleRunbookRequest(resolvedPath, false, true))

// Set up common routes
setupCommonRoutes(r, resolvedPath, outputPath, registry, true)
setupCommonRoutes(r, resolvedPath, outputPath, registry, sessionManager, true)

// listen and serve on localhost:$port only (security: prevent remote access)
return r.Run("127.0.0.1:" + fmt.Sprintf("%d", port))
Expand All @@ -129,6 +146,9 @@ func StartBackendServer(runbookPath string, port int, outputPath string) error {
return fmt.Errorf("failed to create executable registry: %w", err)
}

// Create session manager for persistent environment
sessionManager := NewSessionManager()

// Keep debug mode for development (default behavior)
r := gin.Default()

Expand All @@ -149,7 +169,7 @@ func StartBackendServer(runbookPath string, port int, outputPath string) error {
r.GET("/api/runbook", HandleRunbookRequest(resolvedPath, false, true))

// Set up common routes (includes all other endpoints)
setupCommonRoutes(r, resolvedPath, outputPath, registry, true)
setupCommonRoutes(r, resolvedPath, outputPath, registry, sessionManager, true)

// listen and serve on localhost:$port only (security: prevent remote access)
return r.Run("127.0.0.1:" + fmt.Sprintf("%d", port))
Expand All @@ -172,6 +192,9 @@ func StartServerWithWatch(runbookPath string, port int, outputPath string, useEx
}
}

// Create session manager for persistent environment
sessionManager := NewSessionManager()

// Create file watcher
fileWatcher, err := NewFileWatcher(resolvedPath)
if err != nil {
Expand All @@ -192,7 +215,7 @@ func StartServerWithWatch(runbookPath string, port int, outputPath string, useEx
r.GET("/api/watch", HandleWatchSSE(fileWatcher))

// Set up common routes
setupCommonRoutes(r, resolvedPath, outputPath, registry, useExecutableRegistry)
setupCommonRoutes(r, resolvedPath, outputPath, registry, sessionManager, useExecutableRegistry)

// listen and serve on localhost:$port only (security: prevent remote access)
return r.Run("127.0.0.1:" + fmt.Sprintf("%d", port))
Expand Down
Loading