@@ -32,9 +32,11 @@ import (
3232 "context"
3333 "errors"
3434 "fmt"
35+ "io"
3536 "net/http"
3637 "os"
3738 "os/signal"
39+ "path/filepath"
3840 "syscall"
3941 "time"
4042
@@ -43,6 +45,8 @@ import (
4345 "github.com/voidrunnerhq/voidrunner/internal/auth"
4446 "github.com/voidrunnerhq/voidrunner/internal/config"
4547 "github.com/voidrunnerhq/voidrunner/internal/database"
48+ "github.com/voidrunnerhq/voidrunner/internal/executor"
49+ "github.com/voidrunnerhq/voidrunner/internal/services"
4650 "github.com/voidrunnerhq/voidrunner/pkg/logger"
4751)
4852
@@ -95,12 +99,111 @@ func main() {
9599 // Initialize authentication service
96100 authService := auth .NewService (repos .Users , jwtService , log .Logger , cfg )
97101
102+ // Initialize executor configuration
103+ executorConfig := & executor.Config {
104+ DockerEndpoint : cfg .Executor .DockerEndpoint ,
105+ DefaultResourceLimits : executor.ResourceLimits {
106+ MemoryLimitBytes : int64 (cfg .Executor .DefaultMemoryLimitMB ) * 1024 * 1024 ,
107+ CPUQuota : cfg .Executor .DefaultCPUQuota ,
108+ PidsLimit : cfg .Executor .DefaultPidsLimit ,
109+ TimeoutSeconds : cfg .Executor .DefaultTimeoutSeconds ,
110+ },
111+ DefaultTimeoutSeconds : cfg .Executor .DefaultTimeoutSeconds ,
112+ Images : executor.ImageConfig {
113+ Python : cfg .Executor .PythonImage ,
114+ Bash : cfg .Executor .BashImage ,
115+ JavaScript : cfg .Executor .JavaScriptImage ,
116+ Go : cfg .Executor .GoImage ,
117+ },
118+ Security : executor.SecuritySettings {
119+ EnableSeccomp : cfg .Executor .EnableSeccomp ,
120+ SeccompProfilePath : cfg .Executor .SeccompProfilePath ,
121+ EnableAppArmor : cfg .Executor .EnableAppArmor ,
122+ AppArmorProfile : cfg .Executor .AppArmorProfile ,
123+ ExecutionUser : cfg .Executor .ExecutionUser ,
124+ },
125+ }
126+
127+ // Create seccomp profile directory if it doesn't exist
128+ if cfg .Executor .EnableSeccomp {
129+ seccompDir := filepath .Dir (cfg .Executor .SeccompProfilePath )
130+ if err := os .MkdirAll (seccompDir , 0750 ); err != nil {
131+ log .Warn ("failed to create seccomp profile directory" , "error" , err , "path" , seccompDir )
132+ }
133+
134+ // Create a temporary security manager to generate the seccomp profile
135+ tempSecurityManager := executor .NewSecurityManager (executorConfig )
136+ seccompProfilePath , err := tempSecurityManager .CreateSeccompProfile (context .Background ())
137+ if err != nil {
138+ log .Warn ("failed to create seccomp profile" , "error" , err )
139+ } else {
140+ // Copy the profile to the configured location
141+ if seccompProfilePath != cfg .Executor .SeccompProfilePath {
142+ if err := copyFile (seccompProfilePath , cfg .Executor .SeccompProfilePath ); err != nil {
143+ log .Warn ("failed to copy seccomp profile to configured location" , "error" , err )
144+ } else {
145+ log .Info ("seccomp profile created successfully" , "path" , cfg .Executor .SeccompProfilePath )
146+ }
147+ // Clean up temporary profile
148+ _ = os .Remove (seccompProfilePath )
149+ } else {
150+ log .Info ("seccomp profile created successfully" , "path" , cfg .Executor .SeccompProfilePath )
151+ }
152+ }
153+ }
154+
155+ // Initialize executor (Docker or Mock based on availability)
156+ var taskExecutor executor.TaskExecutor
157+
158+ // Try to initialize Docker executor first
159+ dockerExecutor , err := executor .NewExecutor (executorConfig , log .Logger )
160+ if err != nil {
161+ log .Warn ("failed to initialize Docker executor, falling back to mock executor" , "error" , err )
162+ // Use mock executor for environments without Docker (e.g., CI)
163+ taskExecutor = executor .NewMockExecutor (executorConfig , log .Logger )
164+ log .Info ("mock executor initialized successfully" )
165+ } else {
166+ // Check Docker executor health
167+ healthCtx , healthCancel := context .WithTimeout (context .Background (), 10 * time .Second )
168+ defer healthCancel ()
169+
170+ if err := dockerExecutor .IsHealthy (healthCtx ); err != nil {
171+ log .Warn ("Docker executor health check failed, falling back to mock executor" , "error" , err )
172+ // Cleanup failed Docker executor
173+ _ = dockerExecutor .Cleanup (context .Background ())
174+ // Use mock executor instead
175+ taskExecutor = executor .NewMockExecutor (executorConfig , log .Logger )
176+ log .Info ("mock executor initialized successfully" )
177+ } else {
178+ taskExecutor = dockerExecutor
179+ log .Info ("Docker executor initialized successfully" )
180+ // Add cleanup for successful Docker executor
181+ defer func () {
182+ if err := dockerExecutor .Cleanup (context .Background ()); err != nil {
183+ log .Error ("failed to cleanup Docker executor" , "error" , err )
184+ }
185+ }()
186+ }
187+ }
188+
189+ // Initialize task execution service
190+ taskExecutionService := services .NewTaskExecutionService (dbConn , log .Logger )
191+
192+ // Initialize task executor service
193+ taskExecutorService := services .NewTaskExecutorService (
194+ taskExecutionService ,
195+ repos .Tasks ,
196+ taskExecutor ,
197+ nil , // cleanup manager will be initialized within the executor
198+ log .Logger ,
199+ )
200+
98201 if cfg .IsProduction () {
99202 gin .SetMode (gin .ReleaseMode )
100203 }
101204
102205 router := gin .New ()
103- routes .Setup (router , cfg , log , dbConn , repos , authService )
206+ routes .Setup (router , cfg , log , dbConn , repos , authService , taskExecutionService , taskExecutorService )
104207
105208 srv := & http.Server {
106209 Addr : fmt .Sprintf ("%s:%s" , cfg .Server .Host , cfg .Server .Port ),
@@ -140,3 +243,41 @@ func main() {
140243
141244 log .Info ("server exited" )
142245}
246+
247+ // copyFile copies a file from src to dst with proper path validation
248+ func copyFile (src , dst string ) error {
249+ // Validate and clean paths to prevent directory traversal
250+ cleanSrc := filepath .Clean (src )
251+ cleanDst := filepath .Clean (dst )
252+
253+ // Additional security check: ensure paths don't contain ".." or other suspicious patterns
254+ if ! filepath .IsAbs (cleanSrc ) || ! filepath .IsAbs (cleanDst ) {
255+ return fmt .Errorf ("paths must be absolute" )
256+ }
257+ // #nosec G304 - Path traversal mitigation: paths are validated and cleaned above
258+ sourceFile , err := os .Open (cleanSrc )
259+ if err != nil {
260+ return err
261+ }
262+ defer sourceFile .Close ()
263+
264+ // Ensure destination directory exists
265+ if err := os .MkdirAll (filepath .Dir (cleanDst ), 0750 ); err != nil {
266+ return err
267+ }
268+
269+ // #nosec G304 - Path traversal mitigation: paths are validated and cleaned above
270+ destFile , err := os .Create (cleanDst )
271+ if err != nil {
272+ return err
273+ }
274+ defer destFile .Close ()
275+
276+ _ , err = io .Copy (destFile , sourceFile )
277+ if err != nil {
278+ return err
279+ }
280+
281+ // Set file permissions to 0600 for security
282+ return os .Chmod (cleanDst , 0600 )
283+ }
0 commit comments