Major refactoring

This commit is contained in:
2026-03-04 18:28:52 +01:00
parent 2cbab1a0a2
commit 45495f4b47
21 changed files with 885 additions and 15 deletions

43
executor/executor.go Normal file
View File

@@ -0,0 +1,43 @@
package executor
import (
"fmt"
"net"
"sync"
pb "git.nevets.tech/Keys/CertManager/proto/v1"
"github.com/coreos/go-systemd/v22/activation"
"google.golang.org/grpc"
)
type Server struct {
listener net.Listener
wg sync.WaitGroup
}
func (s *Server) Start() error {
listeners, err := activation.Listeners()
if err != nil {
return fmt.Errorf("systemd activation listeners: %v", err)
}
if len(listeners) != 1 {
return fmt.Errorf("systemd activation listeners: expected 1, got %d", len(listeners))
}
s.listener = listeners[0]
srv := grpc.NewServer()
pb.RegisterHookServiceServer(srv, &hookServer{})
err = srv.Serve(s.listener)
if err != nil {
return fmt.Errorf("error creating grpc listener: %v", err)
}
fmt.Printf("Started gRPC server on %s\n", s.listener.Addr())
return nil
}
func (s *Server) Stop() {
if s.listener != nil {
_ = s.listener.Close()
}
}

73
executor/hook.go Normal file
View File

@@ -0,0 +1,73 @@
package executor
import (
"context"
"errors"
"os"
"os/exec"
"syscall"
"time"
"git.nevets.tech/Keys/CertManager/internal"
pb "git.nevets.tech/Keys/CertManager/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 := internal.MakeCredential(h.GetUser(), h.GetGroup())
if err != nil {
return &pb.ExecuteHookResponse{Error: brief(err)}, nil
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: cred,
}
}
// Were 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
}

8
executor/util.go Normal file
View File

@@ -0,0 +1,8 @@
package executor
import "fmt"
// brief tries to keep errors short and non-leaky.
func brief(err error) string {
return fmt.Sprintf("hook failed: %v", err)
}