Files
elmprodvpn/selective-vpn-api/app/transportcfg/singbox_binary_check.go

83 lines
1.9 KiB
Go

package transportcfg
import (
"os"
"strings"
"time"
)
type RunCommandTimeoutFunc func(timeout time.Duration, name string, args ...string) (stdout string, stderr string, exitCode int, err error)
func ValidateRenderedConfigWithBinary(
config map[string]any,
run RunCommandTimeoutFunc,
timeout time.Duration,
binaryCandidates ...string,
) *SingBoxIssue {
if len(config) == 0 {
return &SingBoxIssue{
Field: "config",
Severity: "error",
Code: "SINGBOX_RENDER_EMPTY",
Message: "rendered config is empty",
}
}
bin, _ := FirstExistingBinaryCandidate(binaryCandidates...)
if strings.TrimSpace(bin) == "" {
return &SingBoxIssue{
Field: "binary",
Severity: "warning",
Code: "SINGBOX_BINARY_MISSING",
Message: "sing-box binary not found; binary check skipped",
}
}
if run == nil {
return &SingBoxIssue{
Field: "binary",
Severity: "warning",
Code: "SINGBOX_CHECK_RUNNER_MISSING",
Message: "command runner is not configured; binary check skipped",
}
}
tmp, err := os.CreateTemp("", "svpn-singbox-check-*.json")
if err != nil {
return &SingBoxIssue{
Field: "binary",
Severity: "warning",
Code: "SINGBOX_CHECK_TEMPFILE_FAILED",
Message: err.Error(),
}
}
tmpPath := tmp.Name()
_ = tmp.Close()
defer os.Remove(tmpPath)
if err := WriteJSONConfigFile(tmpPath, config); err != nil {
return &SingBoxIssue{
Field: "binary",
Severity: "warning",
Code: "SINGBOX_CHECK_TEMPFILE_FAILED",
Message: err.Error(),
}
}
stdout, _, code, runErr := run(timeout, bin, "check", "-c", tmpPath)
if runErr == nil && code == 0 {
return nil
}
msg := strings.TrimSpace(stdout)
if msg == "" && runErr != nil {
msg = runErr.Error()
}
if msg == "" {
msg = "sing-box check failed"
}
return &SingBoxIssue{
Field: "config",
Severity: "error",
Code: "SINGBOX_CHECK_FAILED",
Message: msg,
}
}