167 lines
3.7 KiB
Go
167 lines
3.7 KiB
Go
package app
|
|
|
|
import (
|
|
transportcfgpkg "selective-vpn-api/app/transportcfg"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func deriveSingBoxProfileID(name, protocol string, existing []SingBoxProfile) string {
|
|
base := sanitizeID(name)
|
|
if base == "" {
|
|
base = sanitizeID(protocol)
|
|
}
|
|
if base == "" {
|
|
base = "singbox-profile"
|
|
}
|
|
cand := base
|
|
|
|
used := make(map[string]struct{}, len(existing))
|
|
for _, it := range existing {
|
|
used[it.ID] = struct{}{}
|
|
}
|
|
if _, ok := used[cand]; !ok {
|
|
return cand
|
|
}
|
|
for i := 2; i < 1000; i++ {
|
|
v := base + "-" + strconv.Itoa(i)
|
|
if _, ok := used[v]; !ok {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func findSingBoxProfileIndex(items []SingBoxProfile, id string) int {
|
|
for i := range items {
|
|
if strings.TrimSpace(items[i].ID) == id {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func ensureSingBoxProfilesActiveID(st *singBoxProfilesState) {
|
|
if st == nil {
|
|
return
|
|
}
|
|
if len(st.Items) == 0 {
|
|
st.ActiveProfileID = ""
|
|
return
|
|
}
|
|
active := strings.TrimSpace(st.ActiveProfileID)
|
|
if active != "" {
|
|
for _, it := range st.Items {
|
|
if it.ID == active && it.Enabled {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
for _, it := range st.Items {
|
|
if it.Enabled {
|
|
st.ActiveProfileID = it.ID
|
|
return
|
|
}
|
|
}
|
|
st.ActiveProfileID = st.Items[0].ID
|
|
}
|
|
|
|
func normalizeSingBoxProfilesState(in singBoxProfilesState) singBoxProfilesState {
|
|
out := in
|
|
if out.Version == 0 {
|
|
out.Version = singBoxProfilesStateVersion
|
|
}
|
|
if out.Revision < 0 {
|
|
out.Revision = 0
|
|
}
|
|
|
|
byID := map[string]SingBoxProfile{}
|
|
for _, raw := range in.Items {
|
|
it := normalizeSingBoxProfile(raw)
|
|
if it.ID == "" {
|
|
continue
|
|
}
|
|
cur, ok := byID[it.ID]
|
|
if !ok || preferSingBoxProfile(it, cur) {
|
|
byID[it.ID] = it
|
|
}
|
|
}
|
|
keys := make([]string, 0, len(byID))
|
|
for id := range byID {
|
|
keys = append(keys, id)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
out.Items = make([]SingBoxProfile, 0, len(keys))
|
|
for _, id := range keys {
|
|
out.Items = append(out.Items, byID[id])
|
|
}
|
|
ensureSingBoxProfilesActiveID(&out)
|
|
return out
|
|
}
|
|
|
|
func normalizeSingBoxProfile(in SingBoxProfile) SingBoxProfile {
|
|
out := in
|
|
out.ID = sanitizeID(in.ID)
|
|
if out.ID == "" {
|
|
return SingBoxProfile{}
|
|
}
|
|
out.Name = strings.TrimSpace(in.Name)
|
|
if out.Name == "" {
|
|
out.Name = out.ID
|
|
}
|
|
if mode, ok := normalizeSingBoxProfileMode(in.Mode); ok {
|
|
out.Mode = mode
|
|
} else {
|
|
out.Mode = SingBoxProfileModeTyped
|
|
}
|
|
out.Protocol = normalizeSingBoxProtocol(in.Protocol)
|
|
out.SchemaVersion = normalizeSingBoxSchemaVersion(in.SchemaVersion)
|
|
if out.ProfileRevision <= 0 {
|
|
out.ProfileRevision = 1
|
|
}
|
|
if out.RenderRevision < 0 {
|
|
out.RenderRevision = 0
|
|
}
|
|
out.LastValidatedAt = strings.TrimSpace(in.LastValidatedAt)
|
|
out.LastAppliedAt = strings.TrimSpace(in.LastAppliedAt)
|
|
out.LastError = strings.TrimSpace(in.LastError)
|
|
out.CreatedAt = strings.TrimSpace(in.CreatedAt)
|
|
out.UpdatedAt = strings.TrimSpace(in.UpdatedAt)
|
|
out.Typed = cloneMapDeep(in.Typed)
|
|
out.RawConfig = cloneMapDeep(in.RawConfig)
|
|
out.Meta = cloneMapDeep(in.Meta)
|
|
|
|
secretMap, err := readSingBoxSecrets(out.ID)
|
|
if err == nil {
|
|
out.HasSecrets = len(secretMap) > 0
|
|
out.SecretsMasked = transportcfgpkg.MaskStringMap(secretMap, "******")
|
|
} else {
|
|
out.HasSecrets = in.HasSecrets
|
|
out.SecretsMasked = transportcfgpkg.CloneStringMap(in.SecretsMasked)
|
|
}
|
|
if !out.HasSecrets {
|
|
out.SecretsMasked = nil
|
|
}
|
|
return out
|
|
}
|
|
|
|
func preferSingBoxProfile(cand, cur SingBoxProfile) bool {
|
|
if cand.ProfileRevision != cur.ProfileRevision {
|
|
return cand.ProfileRevision > cur.ProfileRevision
|
|
}
|
|
cu := strings.TrimSpace(cand.UpdatedAt)
|
|
ou := strings.TrimSpace(cur.UpdatedAt)
|
|
if cu != ou {
|
|
if cu == "" {
|
|
return false
|
|
}
|
|
if ou == "" {
|
|
return true
|
|
}
|
|
return cu > ou
|
|
}
|
|
return strings.TrimSpace(cand.CreatedAt) > strings.TrimSpace(cur.CreatedAt)
|
|
}
|