package executor import ( "context" "errors" "os" "os/exec" "syscall" "time" "git.nevets.tech/Steven/certman/common" pb "git.nevets.tech/Steven/certman/proto/v1" ) type hookServer struct { pb.UnimplementedHookServiceServer } func (s *hookServer) ExecuteHook(ctx context.Context, req *pb.ExecuteHookRequest) (*pb.ExecuteHookResponse, error) { h := req.GetHook() if h == nil { return &pb.ExecuteHookResponse{Error: "missing hook"}, nil } // Minimal validation if len(h.GetCommand()) == 0 { return &pb.ExecuteHookResponse{Error: "command is empty"}, nil } // Timeout timeout := time.Duration(h.GetTimeoutSeconds()) * time.Second if timeout <= 0 { timeout = 30 * time.Second } ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() // Build command cmdArgs := h.GetCommand() cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...) if cwd := h.GetCwd(); cwd != "" { cmd.Dir = cwd } // Env: inherit current + overlay provided env := os.Environ() for k, v := range h.GetEnv() { env = append(env, k+"="+v) } cmd.Env = env // Run as user/group if specified (Linux/Unix) if h.GetUser() != "" || h.GetGroup() != "" { cred, err := common.MakeCredential(h.GetUser(), h.GetGroup()) if err != nil { return &pb.ExecuteHookResponse{Error: brief(err)}, nil } cmd.SysProcAttr = &syscall.SysProcAttr{ Credential: cred, } } // We’re intentionally NOT returning stdout/stderr; only a brief error on failure. if err := cmd.Run(); err != nil { // If context deadline hit, make the error message short and explicit. if errors.Is(ctx.Err(), context.DeadlineExceeded) { return &pb.ExecuteHookResponse{Error: "hook timed out"}, nil } return &pb.ExecuteHookResponse{Error: brief(err)}, nil } return &pb.ExecuteHookResponse{Error: ""}, nil }