mirror of
https://github.com/wso2/open-mcp-auth-proxy.git
synced 2025-06-27 17:13:31 +00:00
Merge pull request #31 from pcnfernando/main
This commit is contained in:
commit
edd3ce483e
7 changed files with 309 additions and 90 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -36,3 +36,7 @@ coverage.html
|
||||||
|
|
||||||
# IDE files
|
# IDE files
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# node modules
|
||||||
|
node_modules
|
||||||
|
openmcpauthproxy
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -24,9 +24,9 @@ TEST_OPTS := -v -race
|
||||||
.PHONY: all clean test fmt lint vet coverage help
|
.PHONY: all clean test fmt lint vet coverage help
|
||||||
|
|
||||||
# Default target
|
# Default target
|
||||||
all: lint test build-linux build-linux-arm build-darwin
|
all: lint test build-linux build-linux-arm build-darwin build-windows
|
||||||
|
|
||||||
build: clean test build-linux build-linux-arm build-darwin
|
build: clean test build-linux build-linux-arm build-darwin build-windows
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
mkdir -p $(BUILD_DIR)/linux
|
mkdir -p $(BUILD_DIR)/linux
|
||||||
|
@ -46,6 +46,12 @@ build-darwin:
|
||||||
-o $(BUILD_DIR)/darwin/openmcpauthproxy $(PROJECT_ROOT)/cmd/proxy
|
-o $(BUILD_DIR)/darwin/openmcpauthproxy $(PROJECT_ROOT)/cmd/proxy
|
||||||
cp config.yaml $(BUILD_DIR)/darwin
|
cp config.yaml $(BUILD_DIR)/darwin
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
mkdir -p $(BUILD_DIR)/windows
|
||||||
|
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -x -ldflags "-X main.version=$(BUILD_VERSION)" \
|
||||||
|
-o $(BUILD_DIR)/windows/openmcpauthproxy.exe ./cmd/proxy
|
||||||
|
cp config.yaml $(BUILD_DIR)/windows
|
||||||
|
|
||||||
# Clean build artifacts
|
# Clean build artifacts
|
||||||
clean:
|
clean:
|
||||||
@echo "Cleaning build artifacts..."
|
@echo "Cleaning build artifacts..."
|
||||||
|
|
112
README.md
112
README.md
|
@ -51,10 +51,16 @@ Open MCP Auth Proxy sits between MCP clients and your MCP server to:
|
||||||
|
|
||||||
2. Start the proxy in demo mode (uses pre-configured authentication with Asgardeo sandbox):
|
2. Start the proxy in demo mode (uses pre-configured authentication with Asgardeo sandbox):
|
||||||
|
|
||||||
|
#### Linux/macOS:
|
||||||
```bash
|
```bash
|
||||||
./openmcpauthproxy --demo
|
./openmcpauthproxy --demo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Windows:
|
||||||
|
```powershell
|
||||||
|
.\openmcpauthproxy.exe --demo
|
||||||
|
```
|
||||||
|
|
||||||
> The repository comes with a default `config.yaml` file that contains the basic configuration:
|
> The repository comes with a default `config.yaml` file that contains the basic configuration:
|
||||||
>
|
>
|
||||||
> ```yaml
|
> ```yaml
|
||||||
|
@ -215,12 +221,104 @@ asgardeo:
|
||||||
client_id: "<client_id>"
|
client_id: "<client_id>"
|
||||||
client_secret: "<client_secret>"
|
client_secret: "<client_secret>"
|
||||||
```
|
```
|
||||||
|
## Build from Source
|
||||||
|
|
||||||
### Build from source
|
### Prerequisites for Building
|
||||||
|
|
||||||
```bash
|
* Go 1.20 or higher
|
||||||
git clone https://github.com/wso2/open-mcp-auth-proxy
|
* Git
|
||||||
cd open-mcp-auth-proxy
|
* Make (for Linux/macOS builds)
|
||||||
go get github.com/golang-jwt/jwt/v4 gopkg.in/yaml.v2
|
|
||||||
go build -o openmcpauthproxy ./cmd/proxy
|
### Building on Linux/macOS
|
||||||
```
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/wso2/open-mcp-auth-proxy
|
||||||
|
cd open-mcp-auth-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
go get -v -t -d ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build for your platform:
|
||||||
|
```bash
|
||||||
|
# Build for all platforms
|
||||||
|
make all
|
||||||
|
|
||||||
|
# Or build for a specific platform
|
||||||
|
make build-linux # For Linux
|
||||||
|
make build-darwin # For macOS
|
||||||
|
make build-linux-arm # For ARM-based Linux
|
||||||
|
make build-windows # For Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Find your build in the `build` directory:
|
||||||
|
```bash
|
||||||
|
# For Linux
|
||||||
|
./build/linux/openmcpauthproxy --demo
|
||||||
|
|
||||||
|
# For macOS
|
||||||
|
./build/darwin/openmcpauthproxy --demo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building on Windows
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```powershell
|
||||||
|
git clone https://github.com/wso2/open-mcp-auth-proxy
|
||||||
|
cd open-mcp-auth-proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies:
|
||||||
|
```powershell
|
||||||
|
go get -v -t -d ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Option 1: Build using Make if you have it installed:
|
||||||
|
```powershell
|
||||||
|
make build-windows
|
||||||
|
```
|
||||||
|
|
||||||
|
Option 2: Build manually without Make:
|
||||||
|
```powershell
|
||||||
|
mkdir -p build\windows
|
||||||
|
go build -o build\windows\openmcpauthproxy.exe .\cmd\proxy
|
||||||
|
copy config.yaml build\windows\
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run the built application:
|
||||||
|
```powershell
|
||||||
|
cd build\windows
|
||||||
|
.\openmcpauthproxy.exe --demo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Starting the Proxy on Windows
|
||||||
|
|
||||||
|
1. Open Command Prompt or PowerShell
|
||||||
|
2. Navigate to the build directory:
|
||||||
|
```powershell
|
||||||
|
cd build\windows
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run the executable with your desired options:
|
||||||
|
```powershell
|
||||||
|
# Start in demo mode (using Asgardeo sandbox)
|
||||||
|
openmcpauthproxy.exe --demo
|
||||||
|
|
||||||
|
# Start with Asgardeo integration
|
||||||
|
openmcpauthproxy.exe --asgardeo
|
||||||
|
|
||||||
|
# Start in stdio mode
|
||||||
|
openmcpauthproxy.exe --demo --stdio
|
||||||
|
|
||||||
|
# Enable debug logging
|
||||||
|
openmcpauthproxy.exe --demo --debug
|
||||||
|
|
||||||
|
# See all available options
|
||||||
|
openmcpauthproxy.exe --help
|
||||||
|
```
|
||||||
|
|
||||||
|
4. The proxy will start and display messages indicating it's running
|
||||||
|
5. To stop the proxy, press `Ctrl+C` in the command window
|
||||||
|
|
|
@ -3,6 +3,8 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -145,7 +147,16 @@ func (c *Config) BuildExecCommand() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the full command
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// For Windows, we need to properly escape the inner command
|
||||||
|
escapedCommand := strings.ReplaceAll(c.Stdio.UserCommand, `"`, `\"`)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`npx -y supergateway --stdio "%s" --port %d --baseUrl %s --ssePath %s --messagePath %s`,
|
||||||
|
escapedCommand, c.Port, c.BaseURL, c.Paths.SSE, c.Paths.Messages,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
`npx -y supergateway --stdio "%s" --port %d --baseUrl %s --ssePath %s --messagePath %s`,
|
`npx -y supergateway --stdio "%s" --port %d --baseUrl %s --ssePath %s --messagePath %s`,
|
||||||
c.Stdio.UserCommand, c.Port, c.BaseURL, c.Paths.SSE, c.Paths.Messages,
|
c.Stdio.UserCommand, c.Port, c.BaseURL, c.Paths.SSE, c.Paths.Messages,
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/wso2/open-mcp-auth-proxy/internal/config"
|
"github.com/wso2/open-mcp-auth-proxy/internal/config"
|
||||||
"github.com/wso2/open-mcp-auth-proxy/internal/logging"
|
logger "github.com/wso2/open-mcp-auth-proxy/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manager handles starting and graceful shutdown of subprocesses
|
// Manager handles starting and graceful shutdown of subprocesses
|
||||||
|
@ -31,34 +32,39 @@ func NewManager() *Manager {
|
||||||
|
|
||||||
// EnsureDependenciesAvailable checks and installs required package executors
|
// EnsureDependenciesAvailable checks and installs required package executors
|
||||||
func EnsureDependenciesAvailable(command string) error {
|
func EnsureDependenciesAvailable(command string) error {
|
||||||
// Always ensure npx is available regardless of the command
|
// Always ensure npx is available regardless of the command
|
||||||
if _, err := exec.LookPath("npx"); err != nil {
|
if _, err := exec.LookPath("npx"); err != nil {
|
||||||
// npx is not available, check if npm is installed
|
// npx is not available, check if npm is installed
|
||||||
if _, err := exec.LookPath("npm"); err != nil {
|
if _, err := exec.LookPath("npm"); err != nil {
|
||||||
return fmt.Errorf("npx not found and npm not available; please install Node.js from https://nodejs.org/")
|
return fmt.Errorf("npx not found and npm not available; please install Node.js from https://nodejs.org/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to install npx using npm
|
// Try to install npx using npm
|
||||||
logger.Info("npx not found, attempting to install...")
|
logger.Info("npx not found, attempting to install...")
|
||||||
cmd := exec.Command("npm", "install", "-g", "npx")
|
var cmd *exec.Cmd
|
||||||
cmd.Stdout = os.Stdout
|
if runtime.GOOS == "windows" {
|
||||||
cmd.Stderr = os.Stderr
|
cmd = exec.Command("npm.cmd", "install", "-g", "npx")
|
||||||
|
} else {
|
||||||
if err := cmd.Run(); err != nil {
|
cmd = exec.Command("npm", "install", "-g", "npx")
|
||||||
return fmt.Errorf("failed to install npx: %w", err)
|
}
|
||||||
}
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
logger.Info("npx installed successfully")
|
|
||||||
}
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to install npx: %w", err)
|
||||||
// Check if uv is needed based on the command
|
}
|
||||||
if strings.Contains(command, "uv ") {
|
|
||||||
if _, err := exec.LookPath("uv"); err != nil {
|
logger.Info("npx installed successfully")
|
||||||
return fmt.Errorf("command requires uv but it's not installed; please install it following instructions at https://github.com/astral-sh/uv")
|
}
|
||||||
}
|
|
||||||
}
|
// Check if uv is needed based on the command
|
||||||
|
if strings.Contains(command, "uv ") {
|
||||||
return nil
|
if _, err := exec.LookPath("uv"); err != nil {
|
||||||
|
return fmt.Errorf("command requires uv but it's not installed; please install it following instructions at https://github.com/astral-sh/uv")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetShutdownDelay sets the maximum time to wait for graceful shutdown
|
// SetShutdownDelay sets the maximum time to wait for graceful shutdown
|
||||||
|
@ -88,8 +94,13 @@ func (m *Manager) Start(cfg *config.Config) error {
|
||||||
|
|
||||||
logger.Info("Starting subprocess with command: %s", execCommand)
|
logger.Info("Starting subprocess with command: %s", execCommand)
|
||||||
|
|
||||||
// Use the shell to execute the command
|
var cmd *exec.Cmd
|
||||||
cmd := exec.Command("sh", "-c", execCommand)
|
if runtime.GOOS == "windows" {
|
||||||
|
// Use PowerShell on Windows for better quote handling
|
||||||
|
cmd = exec.Command("powershell", "-Command", execCommand)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("sh", "-c", execCommand)
|
||||||
|
}
|
||||||
|
|
||||||
// Set working directory if specified
|
// Set working directory if specified
|
||||||
if cfg.Stdio.WorkDir != "" {
|
if cfg.Stdio.WorkDir != "" {
|
||||||
|
@ -105,8 +116,8 @@ func (m *Manager) Start(cfg *config.Config) error {
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
// Set the process group for proper termination
|
// Set platform-specific process attributes
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
setProcAttr(cmd)
|
||||||
|
|
||||||
// Start the process
|
// Start the process
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
|
@ -117,11 +128,13 @@ func (m *Manager) Start(cfg *config.Config) error {
|
||||||
m.cmd = cmd
|
m.cmd = cmd
|
||||||
logger.Info("Subprocess started with PID: %d", m.process.Pid)
|
logger.Info("Subprocess started with PID: %d", m.process.Pid)
|
||||||
|
|
||||||
// Get and store the process group ID
|
// Get and store the process group ID (Unix) or PID (Windows)
|
||||||
pgid, err := syscall.Getpgid(m.process.Pid)
|
pgid, err := getProcessGroup(m.process.Pid)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
m.processGroup = pgid
|
m.processGroup = pgid
|
||||||
logger.Debug("Process group ID: %d", m.processGroup)
|
if runtime.GOOS != "windows" {
|
||||||
|
logger.Debug("Process group ID: %d", m.processGroup)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Warn("Failed to get process group ID: %v", err)
|
logger.Warn("Failed to get process group ID: %v", err)
|
||||||
m.processGroup = m.process.Pid
|
m.processGroup = m.process.Pid
|
||||||
|
@ -155,7 +168,7 @@ func (m *Manager) IsRunning() bool {
|
||||||
// Shutdown gracefully terminates the subprocess
|
// Shutdown gracefully terminates the subprocess
|
||||||
func (m *Manager) Shutdown() {
|
func (m *Manager) Shutdown() {
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
processToTerminate := m.process // Local copy of the process reference
|
processToTerminate := m.process // Local copy of the process reference
|
||||||
processGroupToTerminate := m.processGroup
|
processGroupToTerminate := m.processGroup
|
||||||
m.mutex.Unlock()
|
m.mutex.Unlock()
|
||||||
|
|
||||||
|
@ -169,48 +182,73 @@ func (m *Manager) Shutdown() {
|
||||||
go func() {
|
go func() {
|
||||||
defer close(terminateComplete)
|
defer close(terminateComplete)
|
||||||
|
|
||||||
// Try graceful termination first with SIGTERM
|
// Try graceful termination first
|
||||||
terminatedGracefully := false
|
terminatedGracefully := false
|
||||||
|
|
||||||
// Try to terminate the process group first
|
if runtime.GOOS == "windows" {
|
||||||
if processGroupToTerminate != 0 {
|
// Windows: Try to terminate the process
|
||||||
err := syscall.Kill(-processGroupToTerminate, syscall.SIGTERM)
|
m.mutex.Lock()
|
||||||
if err != nil {
|
if m.process != nil {
|
||||||
logger.Warn("Failed to send SIGTERM to process group: %v", err)
|
err := m.process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to terminate process: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.mutex.Unlock()
|
||||||
|
|
||||||
// Fallback to terminating just the process
|
// Wait a bit to see if it terminates
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
m.mutex.Lock()
|
||||||
|
if m.process == nil {
|
||||||
|
terminatedGracefully = true
|
||||||
|
m.mutex.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.mutex.Unlock()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unix: Use SIGTERM followed by SIGKILL if necessary
|
||||||
|
// Try to terminate the process group first
|
||||||
|
if processGroupToTerminate != 0 {
|
||||||
|
err := killProcessGroup(processGroupToTerminate, syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to send SIGTERM to process group: %v", err)
|
||||||
|
|
||||||
|
// Fallback to terminating just the process
|
||||||
|
m.mutex.Lock()
|
||||||
|
if m.process != nil {
|
||||||
|
err = m.process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to send SIGTERM to process: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.mutex.Unlock()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Try to terminate just the process
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
if m.process != nil {
|
if m.process != nil {
|
||||||
err = m.process.Signal(syscall.SIGTERM)
|
err := m.process.Signal(syscall.SIGTERM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to send SIGTERM to process: %v", err)
|
logger.Warn("Failed to send SIGTERM to process: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.mutex.Unlock()
|
m.mutex.Unlock()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Try to terminate just the process
|
// Wait for the process to exit gracefully
|
||||||
m.mutex.Lock()
|
for i := 0; i < 10; i++ {
|
||||||
if m.process != nil {
|
time.Sleep(200 * time.Millisecond)
|
||||||
err := m.process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
m.mutex.Lock()
|
||||||
logger.Warn("Failed to send SIGTERM to process: %v", err)
|
if m.process == nil {
|
||||||
|
terminatedGracefully = true
|
||||||
|
m.mutex.Unlock()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
m.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the process to exit gracefully
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
m.mutex.Lock()
|
|
||||||
if m.process == nil {
|
|
||||||
terminatedGracefully = true
|
|
||||||
m.mutex.Unlock()
|
m.mutex.Unlock()
|
||||||
break
|
|
||||||
}
|
}
|
||||||
m.mutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if terminatedGracefully {
|
if terminatedGracefully {
|
||||||
|
@ -221,12 +259,33 @@ func (m *Manager) Shutdown() {
|
||||||
// If the process didn't exit gracefully, force kill
|
// If the process didn't exit gracefully, force kill
|
||||||
logger.Warn("Subprocess didn't exit gracefully, forcing termination...")
|
logger.Warn("Subprocess didn't exit gracefully, forcing termination...")
|
||||||
|
|
||||||
// Try to kill the process group first
|
if runtime.GOOS == "windows" {
|
||||||
if processGroupToTerminate != 0 {
|
// On Windows, Kill() is already forceful
|
||||||
if err := syscall.Kill(-processGroupToTerminate, syscall.SIGKILL); err != nil {
|
m.mutex.Lock()
|
||||||
logger.Warn("Failed to send SIGKILL to process group: %v", err)
|
if m.process != nil {
|
||||||
|
if err := m.process.Kill(); err != nil {
|
||||||
|
logger.Error("Failed to kill process: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.mutex.Unlock()
|
||||||
|
} else {
|
||||||
|
// Unix: Try SIGKILL
|
||||||
|
// Try to kill the process group first
|
||||||
|
if processGroupToTerminate != 0 {
|
||||||
|
if err := killProcessGroup(processGroupToTerminate, syscall.SIGKILL); err != nil {
|
||||||
|
logger.Warn("Failed to send SIGKILL to process group: %v", err)
|
||||||
|
|
||||||
// Fallback to killing just the process
|
// Fallback to killing just the process
|
||||||
|
m.mutex.Lock()
|
||||||
|
if m.process != nil {
|
||||||
|
if err := m.process.Kill(); err != nil {
|
||||||
|
logger.Error("Failed to kill process: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.mutex.Unlock()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Try to kill just the process
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
if m.process != nil {
|
if m.process != nil {
|
||||||
if err := m.process.Kill(); err != nil {
|
if err := m.process.Kill(); err != nil {
|
||||||
|
@ -235,15 +294,6 @@ func (m *Manager) Shutdown() {
|
||||||
}
|
}
|
||||||
m.mutex.Unlock()
|
m.mutex.Unlock()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Try to kill just the process
|
|
||||||
m.mutex.Lock()
|
|
||||||
if m.process != nil {
|
|
||||||
if err := m.process.Kill(); err != nil {
|
|
||||||
logger.Error("Failed to kill process: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.mutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait a bit more to confirm termination
|
// Wait a bit more to confirm termination
|
||||||
|
|
23
internal/subprocess/manager_unix.go
Normal file
23
internal/subprocess/manager_unix.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package subprocess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setProcAttr sets Unix-specific process attributes
|
||||||
|
func setProcAttr(cmd *exec.Cmd) {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getProcessGroup gets the process group ID on Unix systems
|
||||||
|
func getProcessGroup(pid int) (int, error) {
|
||||||
|
return syscall.Getpgid(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// killProcessGroup kills a process group on Unix systems
|
||||||
|
func killProcessGroup(pgid int, signal syscall.Signal) error {
|
||||||
|
return syscall.Kill(-pgid, signal)
|
||||||
|
}
|
27
internal/subprocess/manager_windows.go
Normal file
27
internal/subprocess/manager_windows.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package subprocess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setProcAttr sets Windows-specific process attributes
|
||||||
|
func setProcAttr(cmd *exec.Cmd) {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getProcessGroup returns the PID itself on Windows (no process groups)
|
||||||
|
func getProcessGroup(pid int) (int, error) {
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// killProcessGroup kills a process on Windows (no process groups)
|
||||||
|
func killProcessGroup(pgid int, signal syscall.Signal) error {
|
||||||
|
// On Windows, we'll use the process handle directly
|
||||||
|
// This function shouldn't be called on Windows, but we provide it for compatibility
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue