215 lines
6.2 KiB
Go
215 lines
6.2 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func handleTransportVirtualClientAction(w http.ResponseWriter, r *http.Request, id, action string) bool {
|
|
cid := sanitizeID(id)
|
|
if !isTransportPolicyVirtualClientID(cid) {
|
|
return false
|
|
}
|
|
|
|
switch action {
|
|
case "health":
|
|
handleTransportVirtualClientHealthAction(w, r, cid)
|
|
case "metrics":
|
|
handleTransportVirtualClientMetricsAction(w, r, cid)
|
|
case "provision":
|
|
handleTransportVirtualClientProvisionAction(w, r, cid)
|
|
case "start", "stop", "restart":
|
|
handleTransportVirtualClientLifecycleAction(w, r, cid, action)
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func handleTransportVirtualClientCardGet(w http.ResponseWriter, r *http.Request, id string) bool {
|
|
cid := sanitizeID(id)
|
|
if !isTransportPolicyVirtualClientID(cid) {
|
|
return false
|
|
}
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return true
|
|
}
|
|
item := transportVirtualAdGuardSnapshot()
|
|
writeJSON(w, http.StatusOK, TransportClientsResponse{OK: true, Message: "ok", Item: &item})
|
|
return true
|
|
}
|
|
|
|
func transportVirtualClientReadOnlyResponse() (int, TransportClientsResponse) {
|
|
return http.StatusOK, TransportClientsResponse{
|
|
OK: false,
|
|
Message: "virtual control-plane client is read-only",
|
|
}
|
|
}
|
|
|
|
func transportVirtualAdGuardSnapshot() TransportClient {
|
|
if item, ok := resolveTransportPolicyVirtualClient(transportPolicyTargetAdGuardID); ok {
|
|
return item
|
|
}
|
|
now := time.Now().UTC()
|
|
item := buildTransportPolicyAdGuardTargetFromObservation("inactive", "DISCONNECTED", "", now)
|
|
item.Health.LastError = "adguard autoloop state unavailable"
|
|
item.Runtime.LastError = TransportClientError{
|
|
Code: "BACKEND_RUNTIME_ERROR",
|
|
Message: item.Health.LastError,
|
|
Retryable: true,
|
|
At: item.Health.LastCheck,
|
|
}
|
|
item.Runtime.LastAction = "observe"
|
|
item.Runtime.LastActionAt = item.Health.LastCheck
|
|
item.UpdatedAt = item.Health.LastCheck
|
|
return item
|
|
}
|
|
|
|
func handleTransportVirtualClientHealthAction(w http.ResponseWriter, r *http.Request, id string) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
item := transportVirtualAdGuardSnapshot()
|
|
resp := buildTransportHealthResponse(item, time.Now().UTC())
|
|
if normalizeTransportStatus(item.Status) == TransportClientDown {
|
|
resp.Code = "TRANSPORT_CLIENT_DOWN"
|
|
}
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
func handleTransportVirtualClientMetricsAction(w http.ResponseWriter, r *http.Request, id string) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
item := transportVirtualAdGuardSnapshot()
|
|
writeJSON(w, http.StatusOK, buildTransportMetricsResponse(item, time.Now().UTC()))
|
|
}
|
|
|
|
func handleTransportVirtualClientProvisionAction(w http.ResponseWriter, r *http.Request, id string) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
item := transportVirtualAdGuardSnapshot()
|
|
now := time.Now().UTC()
|
|
msg := "virtual adapter uses existing adguard autoloop runtime; explicit provision skipped"
|
|
writeJSON(w, http.StatusOK, TransportClientLifecycleResponse{
|
|
OK: true,
|
|
Message: msg,
|
|
Code: "",
|
|
ClientID: item.ID,
|
|
Kind: item.Kind,
|
|
Action: "provision",
|
|
StatusBefore: item.Status,
|
|
StatusAfter: item.Status,
|
|
Health: item.Health,
|
|
Runtime: transportRuntimeSnapshot(item, now),
|
|
})
|
|
}
|
|
|
|
func handleTransportVirtualClientLifecycleAction(w http.ResponseWriter, r *http.Request, id, action string) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
status, resp := runTransportVirtualClientLifecycleAction(id, action)
|
|
writeJSON(w, status, resp)
|
|
}
|
|
|
|
func runTransportVirtualClientLifecycleAction(id, action string) (int, TransportClientLifecycleResponse) {
|
|
status := http.StatusOK
|
|
resp := TransportClientLifecycleResponse{}
|
|
withTransportIfaceLock(transportPolicyTargetAdGuardIfaceID, func() {
|
|
status, resp = executeTransportVirtualClientLifecycleAction(id, action)
|
|
})
|
|
return status, resp
|
|
}
|
|
|
|
func executeTransportVirtualClientLifecycleAction(id, action string) (int, TransportClientLifecycleResponse) {
|
|
before := transportVirtualAdGuardSnapshot()
|
|
beforeStatus := normalizeTransportStatus(before.Status)
|
|
now := time.Now().UTC()
|
|
|
|
cmdAction, ok := transportVirtualAdGuardSystemdAction(action)
|
|
if !ok {
|
|
return http.StatusNotFound, TransportClientLifecycleResponse{
|
|
OK: false,
|
|
Message: "unknown action",
|
|
Code: "TRANSPORT_ACTION_UNKNOWN",
|
|
}
|
|
}
|
|
|
|
stdout, stderr, exitCode, err := runCommand("systemctl", cmdAction, adgvpnUnit)
|
|
actionOK := err == nil && exitCode == 0
|
|
|
|
after := transportVirtualAdGuardSnapshot()
|
|
afterStatus := normalizeTransportStatus(after.Status)
|
|
if !actionOK && afterStatus == beforeStatus {
|
|
afterStatus = TransportClientDegraded
|
|
after.Status = afterStatus
|
|
}
|
|
|
|
msg := strings.TrimSpace(stdout)
|
|
if msg == "" {
|
|
msg = strings.TrimSpace(stderr)
|
|
}
|
|
if msg == "" && err != nil {
|
|
msg = strings.TrimSpace(err.Error())
|
|
}
|
|
if msg == "" {
|
|
msg = fmt.Sprintf("adguardvpn %s done", cmdAction)
|
|
}
|
|
|
|
code := ""
|
|
if !actionOK {
|
|
code = "TRANSPORT_ADGUARD_ACTION_FAILED"
|
|
}
|
|
|
|
events.push("transport_client_state_changed", map[string]any{
|
|
"id": id,
|
|
"from": beforeStatus,
|
|
"to": afterStatus,
|
|
})
|
|
publishTransportRuntimeObservabilitySnapshotChanged(
|
|
"transport_client_state_changed",
|
|
[]string{id},
|
|
[]string{transportPolicyTargetAdGuardIfaceID},
|
|
)
|
|
_, _ = egressIdentitySWR.queueRefresh([]string{"adguardvpn", "system"}, true)
|
|
|
|
return http.StatusOK, TransportClientLifecycleResponse{
|
|
OK: actionOK,
|
|
Message: msg,
|
|
Code: code,
|
|
ExitCode: exitCode,
|
|
Stdout: stdout,
|
|
Stderr: stderr,
|
|
ClientID: id,
|
|
Kind: after.Kind,
|
|
Action: action,
|
|
StatusBefore: beforeStatus,
|
|
StatusAfter: afterStatus,
|
|
Health: after.Health,
|
|
Runtime: transportRuntimeSnapshot(after, now),
|
|
}
|
|
}
|
|
|
|
func transportVirtualAdGuardSystemdAction(action string) (string, bool) {
|
|
switch strings.ToLower(strings.TrimSpace(action)) {
|
|
case "start":
|
|
return "start", true
|
|
case "stop":
|
|
return "stop", true
|
|
case "restart":
|
|
return "restart", true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|