platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
198
selective-vpn-api/app/transport_handlers_netns_apply.go
Normal file
198
selective-vpn-api/app/transport_handlers_netns_apply.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type transportNetnsToggleClientPlan struct {
|
||||
Item TransportNetnsToggleItem
|
||||
NeedsProvision bool
|
||||
NeedsRestart bool
|
||||
}
|
||||
|
||||
func prepareTransportNetnsToggleClientLocked(
|
||||
it TransportClient,
|
||||
ifaces transportInterfacesState,
|
||||
targetEnabled bool,
|
||||
now time.Time,
|
||||
) (TransportNetnsToggleItem, TransportClient) {
|
||||
item := TransportNetnsToggleItem{
|
||||
OK: true,
|
||||
ClientID: it.ID,
|
||||
Kind: it.Kind,
|
||||
StatusBefore: it.Status,
|
||||
StatusAfter: it.Status,
|
||||
NetnsEnabled: targetEnabled,
|
||||
}
|
||||
if it.Kind != TransportClientSingBox {
|
||||
item.OK = false
|
||||
item.Code = "TRANSPORT_NETNS_KIND_UNSUPPORTED"
|
||||
item.Message = "netns toggle is supported only for singbox clients"
|
||||
return item, it
|
||||
}
|
||||
|
||||
cfg := cloneMap(it.Config)
|
||||
if cfg == nil {
|
||||
cfg = map[string]any{}
|
||||
}
|
||||
changed := false
|
||||
prevEnabled := transportConfigBool(cfg, "netns_enabled")
|
||||
if prevEnabled != targetEnabled {
|
||||
changed = true
|
||||
}
|
||||
cfg["netns_enabled"] = targetEnabled
|
||||
if targetEnabled && strings.TrimSpace(transportConfigString(cfg, "netns_name")) == "" {
|
||||
probe := it
|
||||
probe.Config = cfg
|
||||
binding := resolveTransportIfaceBinding(probe, ifaces)
|
||||
if strings.TrimSpace(binding.NetnsName) != "" {
|
||||
cfg["netns_name"] = binding.NetnsName
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
item.ConfigUpdated = changed
|
||||
if changed {
|
||||
it.Config = cfg
|
||||
it.UpdatedAt = now.Format(time.RFC3339)
|
||||
}
|
||||
if rebound, reboundChanged := applyTransportIfaceBinding(it, ifaces, now); reboundChanged {
|
||||
it = rebound
|
||||
item.ConfigUpdated = true
|
||||
}
|
||||
return item, it
|
||||
}
|
||||
|
||||
func applyTransportNetnsToggleClientPlanLocked(plan transportNetnsToggleClientPlan) TransportNetnsToggleItem {
|
||||
item := plan.Item
|
||||
if !item.OK {
|
||||
return item
|
||||
}
|
||||
|
||||
if plan.NeedsProvision {
|
||||
_, provisionResp := executeTransportClientProvisionActionLocked(item.ClientID)
|
||||
mergeTransportNetnsToggleProvisionResponse(&item, provisionResp)
|
||||
if !item.OK {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
if plan.NeedsRestart {
|
||||
_, restartResp := executeTransportLifecycleActionWithPreflightLocked(item.ClientID, "restart")
|
||||
mergeTransportNetnsToggleRestartResponse(&item, restartResp)
|
||||
}
|
||||
if strings.TrimSpace(item.Message) == "" {
|
||||
item.Message = fmt.Sprintf("netns=%t", item.NetnsEnabled)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func mergeTransportNetnsToggleProvisionResponse(item *TransportNetnsToggleItem, resp TransportClientLifecycleResponse) {
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
item.Provisioned = resp.OK
|
||||
item.Stdout = joinNonEmptyLines(item.Stdout, resp.Stdout)
|
||||
item.Stderr = joinNonEmptyLines(item.Stderr, resp.Stderr)
|
||||
item.StatusAfter = resp.StatusAfter
|
||||
if resp.OK {
|
||||
return
|
||||
}
|
||||
item.OK = false
|
||||
item.Code = strings.TrimSpace(resp.Code)
|
||||
item.Message = strings.TrimSpace(resp.Message)
|
||||
if item.Message == "" {
|
||||
item.Message = "provision failed"
|
||||
}
|
||||
}
|
||||
|
||||
func mergeTransportNetnsToggleRestartResponse(item *TransportNetnsToggleItem, resp TransportClientLifecycleResponse) {
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
item.Restarted = resp.OK
|
||||
item.Stdout = joinNonEmptyLines(item.Stdout, resp.Stdout)
|
||||
item.Stderr = joinNonEmptyLines(item.Stderr, resp.Stderr)
|
||||
item.StatusAfter = resp.StatusAfter
|
||||
if resp.OK {
|
||||
msg := strings.TrimSpace(resp.Message)
|
||||
if msg == "" {
|
||||
msg = "restart done"
|
||||
}
|
||||
item.Message = msg
|
||||
return
|
||||
}
|
||||
item.OK = false
|
||||
item.Code = strings.TrimSpace(resp.Code)
|
||||
item.Message = strings.TrimSpace(resp.Message)
|
||||
if item.Message == "" {
|
||||
item.Message = "restart failed"
|
||||
}
|
||||
}
|
||||
|
||||
func markTransportNetnsTogglePlansSaveFailed(
|
||||
plans []transportNetnsToggleClientPlan,
|
||||
msg string,
|
||||
) []TransportNetnsToggleItem {
|
||||
items := make([]TransportNetnsToggleItem, 0, len(plans))
|
||||
for _, plan := range plans {
|
||||
item := plan.Item
|
||||
if item.OK {
|
||||
item.OK = false
|
||||
item.Code = "TRANSPORT_CLIENT_SAVE_FAILED"
|
||||
item.Message = msg
|
||||
item.Provisioned = false
|
||||
item.Restarted = false
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func refreshTransportNetnsToggleFinalStatuses(resp *TransportNetnsToggleResponse) {
|
||||
if resp == nil || len(resp.Items) == 0 {
|
||||
return
|
||||
}
|
||||
transportMu.Lock()
|
||||
st := loadTransportClientsState()
|
||||
transportMu.Unlock()
|
||||
for i := range resp.Items {
|
||||
idx := findTransportClientIndex(st.Items, resp.Items[i].ClientID)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
resp.Items[i].StatusAfter = st.Items[idx].Status
|
||||
resp.Items[i].NetnsEnabled = transportNetnsEnabled(st.Items[idx])
|
||||
}
|
||||
}
|
||||
|
||||
func finalizeTransportNetnsToggleResponse(resp TransportNetnsToggleResponse) TransportNetnsToggleResponse {
|
||||
resp.Count = len(resp.Items)
|
||||
for _, item := range resp.Items {
|
||||
if item.OK {
|
||||
resp.SuccessCount++
|
||||
} else {
|
||||
resp.FailureCount++
|
||||
}
|
||||
}
|
||||
if resp.Count == 0 {
|
||||
resp.OK = false
|
||||
resp.Code = "TRANSPORT_NETNS_NO_TARGETS"
|
||||
resp.Message = "no singbox clients found"
|
||||
return resp
|
||||
}
|
||||
if resp.FailureCount > 0 {
|
||||
resp.OK = false
|
||||
}
|
||||
if !resp.OK && resp.Code == "" {
|
||||
resp.Code = "TRANSPORT_NETNS_TOGGLE_PARTIAL_FAILED"
|
||||
}
|
||||
resp.Message = fmt.Sprintf(
|
||||
"netns=%t applied: success=%d failed=%d",
|
||||
resp.Enabled,
|
||||
resp.SuccessCount,
|
||||
resp.FailureCount,
|
||||
)
|
||||
return resp
|
||||
}
|
||||
Reference in New Issue
Block a user