Files
elmprodvpn/selective-vpn-api/app/transport_singbox_profiles_eval.go

139 lines
3.6 KiB
Go

package app
import (
"time"
transportcfgpkg "selective-vpn-api/app/transportcfg"
)
type singBoxProfileEvalResult struct {
Valid bool
Config map[string]any
Digest string
Diff SingBoxProfileRenderDiff
Changed bool
Errors []SingBoxProfileIssue
Warnings []SingBoxProfileIssue
}
func evaluateSingBoxProfile(profile SingBoxProfile, checkBinary bool) singBoxProfileEvalResult {
deps := transportcfgpkg.SingBoxProfileEvalDeps{
NormalizeMode: func(mode string) (string, bool) {
nm, ok := normalizeSingBoxProfileMode(SingBoxProfileMode(mode))
return string(nm), ok
},
CloneMapDeep: cloneMapDeep,
AsString: asString,
ProtocolSupported: transportcfgpkg.SingBoxTypedProtocolSupported,
ParsePort: transportcfgpkg.ParsePort,
}
in := transportcfgpkg.SingBoxProfileInput{
ID: profile.ID,
Mode: string(profile.Mode),
Protocol: profile.Protocol,
RawConfig: profile.RawConfig,
Typed: profile.Typed,
}
pkgErrs, pkgWarns := transportcfgpkg.ValidateSingBoxProfile(in, deps)
errs := singBoxIssuesFromPkg(pkgErrs)
warns := singBoxIssuesFromPkg(pkgWarns)
rendered, renderErr := transportcfgpkg.RenderSingBoxProfileConfig(in, deps)
if renderErr == nil {
rendered = transportcfgpkg.NormalizeSingBoxRenderedConfig(rendered, asString)
}
if renderErr != nil {
errs = append(errs, SingBoxProfileIssue{
Field: "profile",
Severity: "error",
Code: "SINGBOX_RENDER_INVALID",
Message: renderErr.Error(),
})
}
if checkBinary && renderErr == nil {
if issue := validateSingBoxWithBinary(rendered); issue != nil {
if issue.Severity == "warning" {
warns = append(warns, *issue)
} else {
errs = append(errs, *issue)
}
}
}
valid := len(errs) == 0
diff := SingBoxProfileRenderDiff{}
digest := ""
changed := false
if rendered != nil {
digest = transportcfgpkg.DigestJSONMap(rendered)
prev := transportcfgpkg.ReadJSONMapFile(singBoxRenderedPath(profile.ID))
pkgDiff, cfgChanged := transportcfgpkg.DiffConfigMaps(prev, rendered)
diff = SingBoxProfileRenderDiff{
Added: pkgDiff.Added,
Removed: pkgDiff.Removed,
Changed: pkgDiff.Changed,
}
changed = cfgChanged
}
return singBoxProfileEvalResult{
Valid: valid,
Config: rendered,
Digest: digest,
Diff: diff,
Changed: changed,
Errors: errs,
Warnings: warns,
}
}
func singBoxIssuesFromPkg(in []transportcfgpkg.SingBoxIssue) []SingBoxProfileIssue {
if len(in) == 0 {
return nil
}
out := make([]SingBoxProfileIssue, 0, len(in))
for _, it := range in {
out = append(out, SingBoxProfileIssue{
Field: it.Field,
Severity: it.Severity,
Code: it.Code,
Message: it.Message,
})
}
return out
}
func validateSingBoxWithBinary(config map[string]any) *SingBoxProfileIssue {
issue := transportcfgpkg.ValidateRenderedConfigWithBinary(
config,
runCommandTimeout,
5*time.Second,
"/usr/local/bin/sing-box",
"/usr/bin/sing-box",
"sing-box",
)
if issue == nil {
return nil
}
return &SingBoxProfileIssue{
Field: issue.Field,
Severity: issue.Severity,
Code: issue.Code,
Message: issue.Message,
}
}
func writeSingBoxRenderedConfig(profileID string, config map[string]any) (string, error) {
path := singBoxRenderedPath(profileID)
if err := transportcfgpkg.WriteJSONConfigFile(path, config); err != nil {
return path, err
}
return path, nil
}
func singBoxRenderedPath(profileID string) string {
return transportcfgpkg.ProfileConfigPath(singBoxRenderedRootDir, profileID, sanitizeID)
}
func defaultSingBoxAppliedConfigPath(profileID string) string {
return transportcfgpkg.ProfileConfigPath(singBoxAppliedRootDir, profileID, sanitizeID)
}