api

package
v0.0.0-...-a130917 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 77 Imported by: 0

Documentation

Overview

internal/api/awg_outbounds.go

Package api — NDMS-specific endpoints (non-routing, non-tunnel).

Currently exposes GET /api/ndms/save-status, which mirrors the former save:status SSE event. The endpoint is part of the state-sync redesign: SaveCoordinator publishes a resource:invalidated hint on state changes and clients refetch this endpoint.

Package api — singbox_proxies.go exposes runtime controls for sing-box composite outbounds (selector / urltest / loadbalance) by relaying typed requests to the upstream Clash API. The handler is kept thin: shape the request, call Clash, shape the response into project-standard envelopes.

Index

Constants

View Source
const (
	ResourceTunnels                 = "tunnels"
	ResourceServers                 = "servers"
	ResourceSingboxStatus           = "singbox.status"
	ResourceSingboxTunnels          = "singbox.tunnels"
	ResourceSysInfo                 = "sysInfo"
	ResourcePingcheck               = "pingcheck"
	ResourceSaveStatus              = "saveStatus"
	ResourceSettings                = "settings"
	ResourceRoutingDnsRoutes        = "routing.dnsRoutes"
	ResourceRoutingStaticRoutes     = "routing.staticRoutes"
	ResourceRoutingAccessPolicies   = "routing.accessPolicies"
	ResourceRoutingPolicyDevices    = "routing.policyDevices"
	ResourceRoutingPolicyInterfaces = "routing.policyInterfaces"
	ResourceRoutingClientRoutes     = "routing.clientRoutes"
	ResourceRoutingTunnels          = "routing.tunnels"
	ResourceRoutingHydrarouteStatus = "routing.hydrarouteStatus"
	ResourceDeviceProxy             = "deviceproxy"
	ResourceDeviceProxyConfig       = "deviceproxy.config"
	ResourceDeviceProxyRuntime      = "deviceproxy.runtime"
)

Resource keys — closed set. Keep in sync with frontend/src/lib/stores/storeRegistry.ts.

Variables

This section is empty.

Functions

func BuildTunnelResponse

func BuildTunnelResponse(r *http.Request, svc TunnelService, store *storage.AWGTunnelStore, id string, quiescentUntil time.Time) (map[string]interface{}, error)

BuildTunnelResponse builds a consistent tunnel response with stored data. Exported so Import and External handlers can reuse the same response format. quiescentUntil is the orchestrator bring-up window (zero when unknown) so the "state" string carries the same boot-pending overlay the list shows.

func ServeOS4EmptyAccessPolicies

func ServeOS4EmptyAccessPolicies(w http.ResponseWriter, r *http.Request)

ServeOS4EmptyAccessPolicies returns an empty access-policies list on OS4 (no NDMS policies API).

@Summary		OS4 access policies stub
@Description	Always {"data":[]}. Real data on KeeneticOS 5 only.
@Tags			routing
@Produce		json
@Security		CookieAuth
@Success		200	{object}	AccessPoliciesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/routing/access-policies [get]

func ServeOS4EmptyPolicyInterfaces

func ServeOS4EmptyPolicyInterfaces(w http.ResponseWriter, r *http.Request)

ServeOS4EmptyPolicyInterfaces returns an empty policy-interfaces list on OS4.

@Summary		OS4 policy interfaces stub
@Description	Always {"data":[]}. Real data on KeeneticOS 5 only.
@Tags			routing
@Produce		json
@Security		CookieAuth
@Success		200	{object}	PolicyInterfacesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/routing/policy-interfaces [get]

func WriteTunnelReferenced

func WriteTunnelReferenced(w http.ResponseWriter, err tunnelservice.ErrTunnelReferenced)

WriteTunnelReferenced responds with HTTP 409 and the structured payload the frontend uses for TunnelReferencedModal.

Types

type APIEnvelope

type APIEnvelope struct {
	Success bool   `json:"success" example:"true"`
	Data    any    `json:"data,omitempty" swaggertype:"object"`
	Message string `json:"message,omitempty" example:"ok"`
}

APIEnvelope describes the common successful API response shape. Most handlers return: { success: true, data: ... }.

type APIErrorEnvelope

type APIErrorEnvelope struct {
	Error   bool   `json:"error" example:"true"`
	Message string `json:"message" example:"invalid request"`
	Code    string `json:"code" example:"BAD_REQUEST"`
}

APIErrorEnvelope describes the common error response shape. Most handlers return: { error: true, message: "...", code: "..." }.

type ASCParamsResponse

type ASCParamsResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    map[string]interface{} `json:"data" swaggertype:"object"`
}

ASCParamsResponse is the envelope for GET /managed-servers/{id}/asc. The data field is the AWG signature/obfuscation params object — its shape depends on the active signature preset, so it is intentionally an opaque object in OpenAPI.

type AWGInterfaceDTO

type AWGInterfaceDTO struct {
	PrivateKey string `json:"privateKey" example:"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="`
	Address    string `json:"address" example:"10.0.0.2/32"`
	MTU        int    `json:"mtu" example:"1420"`
	DNS        string `json:"dns,omitempty" example:"8.8.8.8"`
	Jc         int    `json:"jc" example:"4"`
	Jmin       int    `json:"jmin" example:"40"`
	Jmax       int    `json:"jmax" example:"70"`
	S1         int    `json:"s1" example:"0"`
	S2         int    `json:"s2" example:"0"`
	S3         int    `json:"s3" example:"0"`
	S4         int    `json:"s4" example:"0"`
	H1         string `json:"h1" example:"1"`
	H2         string `json:"h2" example:"2"`
	H3         string `json:"h3" example:"3"`
	H4         string `json:"h4" example:"4"`
}

AWGInterfaceDTO mirrors frontend AWGInterface.

type AWGOutboundTagDTO

type AWGOutboundTagDTO struct {
	Tag   string `json:"tag" example:"awg-vpn0"`
	Label string `json:"label" example:"My VPN"`
	Kind  string `json:"kind" example:"managed"`
	Iface string `json:"iface" example:"nwg0"`
}

AWGOutboundTagDTO mirrors awgoutbounds.TagInfo for OpenAPI exposure.

type AWGOutboundTagsResponse

type AWGOutboundTagsResponse struct {
	Success bool                `json:"success" example:"true"`
	Data    []AWGOutboundTagDTO `json:"data"`
}

AWGOutboundTagsResponse is the envelope shape for /singbox/awg-outbounds/tags.

type AWGOutboundsHandler

type AWGOutboundsHandler struct {
	// contains filtered or unexported fields
}

AWGOutboundsHandler exposes the catalog of AWG-direct outbound tags for the frontend (singbox-router rule editor outbound dropdown).

func NewAWGOutboundsHandler

func NewAWGOutboundsHandler(svc AWGOutboundsService) *AWGOutboundsHandler

func (*AWGOutboundsHandler) ServeHTTP

func (h *AWGOutboundsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP returns the catalog of AWG-direct outbound tags.

@Summary		List AWG outbound tags
@Description	Returns the catalog of AWG-direct outbound tags currently exposed to sing-box (one per managed/system AWG tunnel). Used by the singbox-router rule editor outbound dropdown.
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Success		200	{object}	AWGOutboundTagsResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/awg-outbounds/tags [get]

type AWGOutboundsService

type AWGOutboundsService interface {
	ListTags(ctx context.Context) ([]awgoutbounds.TagInfo, error)
}

AWGOutboundsService is the narrow contract this handler needs. Implemented by awgoutbounds.Service.

type AWGPeerDTO

type AWGPeerDTO struct {
	PublicKey           string   `json:"publicKey" example:"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="`
	Endpoint            string   `json:"endpoint" example:"vpn.example.com:51820"`
	AllowedIPs          []string `json:"allowedIPs" example:"0.0.0.0/0"`
	PersistentKeepalive int      `json:"persistentKeepalive,omitempty" example:"25"`
}

AWGPeerDTO mirrors frontend AWGPeer.

type AWGTunnelDTO

type AWGTunnelDTO struct {
	ID            string              `json:"id" example:"tun_abc123"`
	Name          string              `json:"name" example:"My VPN"`
	Type          string              `json:"type" example:"awg" enums:"awg,wg"`
	Enabled       bool                `json:"enabled" example:"true"`
	DefaultRoute  bool                `json:"defaultRoute" example:"false"`
	InterfaceName string              `json:"interfaceName,omitempty" example:"nwg0"`
	State         string              `json:"state,omitempty" example:"connected"`
	Backend       string              `json:"backend,omitempty" example:"nativewg"`
	Interface     AWGInterfaceDTO     `json:"interface"`
	Peer          AWGPeerDTO          `json:"peer"`
	StateInfo     *TunnelStateInfoDTO `json:"stateInfo,omitempty"`
}

AWGTunnelDTO mirrors frontend AWGTunnel.

type AccessPoliciesListResponse

type AccessPoliciesListResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    []AccessPolicyDTO `json:"data"`
}

AccessPoliciesListResponse is the envelope for GET /access-policies.

type AccessPolicyDTO

type AccessPolicyDTO struct {
	Name        string                     `json:"name" example:"default"`
	Description string                     `json:"description" example:"Default policy"`
	Standalone  bool                       `json:"standalone" example:"false"`
	Interfaces  []AccessPolicyInterfaceDTO `json:"interfaces"`
	DeviceCount int                        `json:"deviceCount" example:"5"`
	IsStandard  bool                       `json:"isStandard" example:"true"`
}

AccessPolicyDTO mirrors frontend AccessPolicy.

type AccessPolicyHandler

type AccessPolicyHandler struct {
	// contains filtered or unexported fields
}

AccessPolicyHandler handles access policy CRUD and device assignment operations.

func NewAccessPolicyHandler

func NewAccessPolicyHandler(svc accesspolicy.Service) *AccessPolicyHandler

NewAccessPolicyHandler creates a new access policy handler.

func (*AccessPolicyHandler) AssignDevice

func (h *AccessPolicyHandler) AssignDevice(w http.ResponseWriter, r *http.Request)

AssignDevice handles device assignment to policies. POST /api/access-policies/assign — assign device Body: {"mac":"AA:BB:CC:DD:EE:FF","policy":"Policy0"} DELETE /api/access-policies/assign?mac=AA:BB:CC:DD:EE:FF — unassign device

func (*AccessPolicyHandler) Create

Create creates a new access policy. POST /api/access-policies/create Body: {"description":"..."}

@Summary		Create access policy
@Tags			access-policy
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		CreateAccessPolicyRequest	true	"Policy description"
@Success		200		{object}	AccessPolicyResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/access-policies/create [post]

func (*AccessPolicyHandler) Delete

Delete removes an access policy. DELETE /api/access-policies/delete?name=Policy0

@Summary		Delete access policy
@Description	Removes the named access policy. Bound LAN devices revert to the default policy.
@Tags			access-policy
@Produce		json
@Security		CookieAuth
@Param			name	query		string	true	"Policy name (e.g. Policy0)"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/access-policies/delete [delete]

func (*AccessPolicyHandler) List

List returns all access policies. GET /api/access-policies

@Summary		List access policies
@Description	KeeneticOS 5 only when route is registered.
@Tags			access-policy
@Produce		json
@Security		CookieAuth
@Param			refresh	query		string	false	"Pass 'true' to bypass NDMS cache"
@Success		200		{object}	AccessPoliciesListResponse
@Failure		500		{object}	APIErrorEnvelope
@Router			/access-policies [get]

func (*AccessPolicyHandler) ListDevices

func (h *AccessPolicyHandler) ListDevices(w http.ResponseWriter, r *http.Request)

ListDevices returns all LAN devices with their policy assignments. GET /api/access-policies/devices

@Summary		List policy devices
@Tags			access-policy
@Produce		json
@Security		CookieAuth
@Param			refresh	query		string	false	"Pass 'true' to bypass NDMS cache"
@Success		200		{object}	PolicyDevicesListResponse
@Failure		500		{object}	APIErrorEnvelope
@Router			/access-policies/devices [get]
@Router			/routing/policy-devices [get]

func (*AccessPolicyHandler) ListGlobalInterfaces

func (h *AccessPolicyHandler) ListGlobalInterfaces(w http.ResponseWriter, r *http.Request)

ListGlobalInterfaces returns all router interfaces available for policy routing. GET /api/access-policies/interfaces

@Summary		List global policy interfaces
@Tags			access-policy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	PolicyInterfacesListResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/access-policies/interfaces [get]

func (*AccessPolicyHandler) PermitInterface

func (h *AccessPolicyHandler) PermitInterface(w http.ResponseWriter, r *http.Request)

PermitInterface handles permit/deny operations for policy interfaces. POST /api/access-policies/permit — add interface DELETE /api/access-policies/permit?name=...&interface=... — remove interface

func (*AccessPolicyHandler) SetDescription

func (h *AccessPolicyHandler) SetDescription(w http.ResponseWriter, r *http.Request)

SetDescription updates the description of an access policy. POST /api/access-policies/description Body: {"name":"Policy0","description":"..."}

@Summary		Set access policy description
@Tags			access-policy
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SetDescriptionRequest	true	"Policy name + new description"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/access-policies/description [post]

func (*AccessPolicyHandler) SetEventBus

func (h *AccessPolicyHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus for SSE publishing.

func (*AccessPolicyHandler) SetInterfaceUp

func (h *AccessPolicyHandler) SetInterfaceUp(w http.ResponseWriter, r *http.Request)

SetInterfaceUp brings an interface up or down. POST /api/access-policies/interface-up Body: {"name":"Wireguard0","up":true}

@Summary		Set interface admin up
@Tags			access-policy
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SetInterfaceUpRequest	true	"Interface name + admin up flag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/access-policies/interface-up [post]

func (*AccessPolicyHandler) SetStandalone

func (h *AccessPolicyHandler) SetStandalone(w http.ResponseWriter, r *http.Request)

SetStandalone enables or disables standalone mode on an access policy. POST /api/access-policies/standalone Body: {"name":"Policy0","enabled":true}

@Summary		Set access policy standalone mode
@Tags			access-policy
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SetStandaloneRequest	true	"Policy name + enabled flag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/access-policies/standalone [post]

type AccessPolicyInterfaceDTO

type AccessPolicyInterfaceDTO struct {
	Name   string `json:"name" example:"nwg0"`
	Label  string `json:"label,omitempty" example:"My VPN"`
	Order  int    `json:"order" example:"1"`
	Denied bool   `json:"denied,omitempty" example:"false"`
}

AccessPolicyInterfaceDTO mirrors frontend AccessPolicyInterface.

type AccessPolicyResponse

type AccessPolicyResponse struct {
	Success bool            `json:"success" example:"true"`
	Data    AccessPolicyDTO `json:"data"`
}

AccessPolicyResponse is the envelope for endpoints that return a single access policy (e.g. POST /access-policies/create).

type ActiveMemberRequest

type ActiveMemberRequest struct {
	MemberTag string `json:"memberTag" example:"sub-demo-001"`
}

ActiveMemberRequest is the body for POST /api/singbox/subscriptions/active-member.

type ActiveNowResponse

type ActiveNowResponse struct {
	Now string `json:"now" example:"sub-abc-aaaa"`
}

ActiveNowResponse is the payload for GET /api/singbox/subscriptions/active-now. Surface only the live "now" pointer from Clash for urltest mode UI.

type AddGeoFileRequest

type AddGeoFileRequest struct {
	Type  string            `json:"type" example:"geosite"`
	URL   string            `json:"url" example:"https://cdn.example.com/geosite.db"`
	Route *DownloadRouteDTO `json:"route,omitempty"`
}

AddGeoFileRequest is the body for POST /hydraroute/geo-files/add.

type AddMemberRequest

type AddMemberRequest struct {
	ShareLink string `json:"shareLink" example:"vless://...@h.example:443?security=tls&sni=h"`
}

AddMemberRequest is the body for POST /api/singbox/subscriptions/members/add. Inline subscriptions only — manual CRUD is rejected on URL-backed subscriptions (the URL refresh diff owns the truth there).

type AddPeerRequestDTO

type AddPeerRequestDTO struct {
	Description string `json:"description" example:"My Phone"`
	TunnelIP    string `json:"tunnelIP" example:"10.10.0.2/32"`
	DNS         string `json:"dns,omitempty" example:"8.8.8.8"`
}

AddPeerRequestDTO is the swagger-visible body for POST /managed-servers/{id}/peers.

type AdoptRequest

type AdoptRequest struct {
	Content string `json:"content"`
	Name    string `json:"name"`
}

AdoptRequest is the request body for adopting an external tunnel.

type AllInterfacesResponse

type AllInterfacesResponse struct {
	Success bool                 `json:"success" example:"true"`
	Data    []RouterInterfaceDTO `json:"data"`
}

AllInterfacesResponse is the envelope for GET /system/all-interfaces.

type AmneziaCPHandler

type AmneziaCPHandler struct {
	// contains filtered or unexported fields
}

AmneziaCPHandler proxies Amnezia Premium customer portal API so the SPA can load country lists and configs without browser CORS issues.

func NewAmneziaCPHandler

func NewAmneziaCPHandler(appLogger logging.AppLogger) *AmneziaCPHandler

func (*AmneziaCPHandler) AccountInfo

func (h *AmneziaCPHandler) AccountInfo(w http.ResponseWriter, r *http.Request)

AccountInfo returns subscription metadata including available_countries (proxied JSON.data).

@Summary	Get Amnezia Premium account info
@Tags		amnezia-premium
@Accept		json
@Produce	json
@Security	CookieAuth
@Param		body	body		AmneziaPremiumAccountInfoRequest	true	"Portal session id returned by login"
@Success	200		{object}	AmneziaPremiumAccountInfoResponse
@Failure	400		{object}	APIErrorEnvelope
@Failure	500		{object}	APIErrorEnvelope
@Router		/amnezia-premium/account-info [post]

func (*AmneziaCPHandler) DownloadConfig

func (h *AmneziaCPHandler) DownloadConfig(w http.ResponseWriter, r *http.Request)

DownloadConfig fetches AWG/WG client config text for a country code.

@Summary	Download Amnezia Premium config
@Tags		amnezia-premium
@Accept		json
@Produce	json
@Security	CookieAuth
@Param		body	body		AmneziaPremiumDownloadConfigRequest	true	"Portal session id and country code"
@Success	200		{object}	AmneziaPremiumDownloadConfigResponse
@Failure	400		{object}	APIErrorEnvelope
@Failure	500		{object}	APIErrorEnvelope
@Router		/amnezia-premium/download-config [post]

func (*AmneziaCPHandler) Login

Login exchanges vpnKey for a portal session JWT (v_sid).

@Summary	Login to Amnezia Premium portal
@Tags		amnezia-premium
@Accept		json
@Produce	json
@Security	CookieAuth
@Param		body	body		AmneziaPremiumLoginRequest	true	"Premium vpn:// key"
@Success	200		{object}	AmneziaPremiumLoginResponse
@Failure	400		{object}	APIErrorEnvelope
@Failure	422		{object}	APIErrorEnvelope
@Failure	500		{object}	APIErrorEnvelope
@Router		/amnezia-premium/login [post]

func (*AmneziaCPHandler) SetDownloader

func (h *AmneziaCPHandler) SetDownloader(svc *downloader.Service)

type AmneziaPremiumAccountInfoRequest

type AmneziaPremiumAccountInfoRequest struct {
	Sid string `json:"sid" example:"v_sid_cookie_value"`
}

AmneziaPremiumAccountInfoRequest is the body for POST /amnezia-premium/account-info.

type AmneziaPremiumAccountInfoResponse

type AmneziaPremiumAccountInfoResponse struct {
	Success bool           `json:"success" example:"true"`
	Data    map[string]any `json:"data" swaggertype:"object"`
}

AmneziaPremiumAccountInfoResponse is a typed envelope around the proxied CP account payload.

type AmneziaPremiumDownloadConfigData

type AmneziaPremiumDownloadConfigData struct {
	Config string `json:"config" example:"[Interface]\nPrivateKey = ..."`
}

AmneziaPremiumDownloadConfigData contains the downloaded WireGuard/AmneziaWG config.

type AmneziaPremiumDownloadConfigRequest

type AmneziaPremiumDownloadConfigRequest struct {
	Sid         string `json:"sid" example:"v_sid_cookie_value"`
	CountryCode string `json:"countryCode" example:"nl"`
}

AmneziaPremiumDownloadConfigRequest is the body for POST /amnezia-premium/download-config.

type AmneziaPremiumDownloadConfigResponse

type AmneziaPremiumDownloadConfigResponse struct {
	Success bool                             `json:"success" example:"true"`
	Data    AmneziaPremiumDownloadConfigData `json:"data"`
}

AmneziaPremiumDownloadConfigResponse is the typed success envelope for config downloads.

type AmneziaPremiumLoginData

type AmneziaPremiumLoginData struct {
	Sid string `json:"sid" example:"v_sid_cookie_value"`
}

AmneziaPremiumLoginData is returned after a successful portal login.

type AmneziaPremiumLoginRequest

type AmneziaPremiumLoginRequest struct {
	VPNKey      string `json:"vpnKey" example:"vpn://..."`
	VPNKeySnake string `json:"vpn_key,omitempty" example:"vpn://..."`
	Remember    *bool  `json:"remember,omitempty" example:"true"`
}

AmneziaPremiumLoginRequest is the body for POST /amnezia-premium/login.

type AmneziaPremiumLoginResponse

type AmneziaPremiumLoginResponse struct {
	Success bool                    `json:"success" example:"true"`
	Data    AmneziaPremiumLoginData `json:"data"`
}

AmneziaPremiumLoginResponse is the typed success envelope for Premium login.

type AssignDeviceRequest

type AssignDeviceRequest struct {
	MAC    string `json:"mac" example:"aa:bb:cc:dd:ee:ff"`
	Policy string `json:"policy" example:"Policy0"`
}

AssignDeviceRequest is the body for POST /access-policies/assign.

type AuthHandler

type AuthHandler struct {
	// contains filtered or unexported fields
}

AuthHandler handles authentication endpoints.

func NewAuthHandler

func NewAuthHandler(keenetic *auth.KeeneticClient, sessions *auth.SessionStore, settings *storage.SettingsStore, appLogger logging.AppLogger) *AuthHandler

NewAuthHandler creates a new auth handler.

func (*AuthHandler) Login

func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request)

Login authenticates against the router and sets the session cookie.

@Summary		Login
@Description	Authenticates with Keenetic credentials; sets HttpOnly session cookie awg_session.
@Tags			auth
@Accept			json
@Produce		json
@Param			body	body		LoginRequest	true	"Router login and password"
@Success		200		{object}	LoginResponseRaw
@Failure		400		{object}	APIErrorEnvelope
@Failure		401		{object}	APIErrorEnvelope
@Failure		503		{object}	APIErrorEnvelope
@Router			/auth/login [post]

func (*AuthHandler) Logout

func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request)

Logout clears the session cookie and invalidates the server-side session.

@Summary		Logout
@Tags			auth
@Produce		json
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/auth/logout [post]

func (*AuthHandler) Status

func (h *AuthHandler) Status(w http.ResponseWriter, r *http.Request)

Status returns whether the client is authenticated and optional session metadata.

@Summary		Auth status
@Tags			auth
@Produce		json
@Success		200	{object}	AuthStatusResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/auth/status [get]

type AuthStatusResponse

type AuthStatusResponse struct {
	Authenticated bool   `json:"authenticated" example:"true"`
	AuthDisabled  bool   `json:"authDisabled" example:"false"`
	Login         string `json:"login,omitempty" example:"admin"`
	ExpiresIn     int    `json:"expiresIn,omitempty" example:"3600"`
}

AuthStatusResponse is the raw (non-enveloped) payload for GET /auth/status.

type BackupWarningDTO

type BackupWarningDTO struct {
	InterfaceName string `json:"interfaceName,omitempty"`
	Message       string `json:"message"`
}

type BootStatusHandler

type BootStatusHandler struct {
	InstanceID string
}

BootStatusHandler serves GET /api/boot-status (public).

func NewBootStatusHandler

func NewBootStatusHandler(instanceID string) *BootStatusHandler

NewBootStatusHandler returns a handler that reports boot phase and instance id.

func (*BootStatusHandler) Get

Get responds with boot readiness and instance id for frontend restart detection.

@Summary		Boot status
@Description	Public snapshot: initializing flag, phase, instance id.
@Tags			system
@Produce		json
@Success		200	{object}	BootStatusResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/boot-status [get]

type BootStatusResponse

type BootStatusResponse struct {
	Initializing     bool   `json:"initializing" example:"false"`
	RemainingSeconds int    `json:"remainingSeconds" example:"0"`
	Phase            string `json:"phase" example:"ready"`
	InstanceId       string `json:"instanceId" example:"a1b2c3d4e5f6"`
}

BootStatusResponse is the raw (non-enveloped) payload for GET /boot-status.

type ChangelogData

type ChangelogData struct {
	Entries []ChangelogEntryDTO `json:"entries"`
}

ChangelogData is the payload for GET /system/update/changelog.

type ChangelogEntryDTO

type ChangelogEntryDTO struct {
	Version string              `json:"version" example:"2.5.0"`
	Date    string              `json:"date" example:"2024-01-15"`
	Groups  []ChangelogGroupDTO `json:"groups"`
}

ChangelogEntryDTO mirrors frontend ChangelogEntry.

type ChangelogGroupDTO

type ChangelogGroupDTO struct {
	Heading string   `json:"heading" example:"Bug Fixes"`
	Items   []string `json:"items" example:"Fixed tunnel restart loop"`
}

ChangelogGroupDTO mirrors frontend ChangelogGroup.

type ChangelogResponse

type ChangelogResponse struct {
	Success bool          `json:"success" example:"true"`
	Data    ChangelogData `json:"data"`
}

ChangelogResponse is the envelope for GET /system/update/changelog.

type ClashProxy

type ClashProxy struct {
	// contains filtered or unexported fields
}

ClashProxy forwards /api/singbox/clash/* to the sing-box embedded Clash API. Supports both plain HTTP (GET/DELETE/POST) and WebSocket upgrade (for /traffic, /connections, /logs endpoints).

func NewClashProxy

func NewClashProxy(op *singbox.Operator) *ClashProxy

NewClashProxy creates a ClashProxy backed by the given sing-box Operator.

func (*ClashProxy) ClashBaseURL

func (p *ClashProxy) ClashBaseURL() string

ClashBaseURL returns the upstream clash_api endpoint, e.g. "http://127.0.0.1:9099". Used by SingboxProxiesHandler to make internal HTTP calls instead of duplicating the URL. Returns an empty string if the underlying ClashClient has no address yet so callers can short-circuit before issuing a malformed request.

func (*ClashProxy) ServeHTTP

func (p *ClashProxy) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP routes HTTP and WebSocket upgrade requests to the Clash upstream.

type ClientRouteDTO

type ClientRouteDTO struct {
	ID             string `json:"id" example:"cr_001"`
	ClientIp       string `json:"clientIp" example:"192.168.1.100"`
	ClientHostname string `json:"clientHostname" example:"My-Phone"`
	TunnelId       string `json:"tunnelId" example:"tun_abc123"`
	Fallback       string `json:"fallback" example:"drop"`
	Enabled        bool   `json:"enabled" example:"true"`
}

ClientRouteDTO mirrors frontend ClientRoute.

type ClientRouteHandler

type ClientRouteHandler struct {
	// contains filtered or unexported fields
}

ClientRouteHandler handles client route CRUD operations.

func NewClientRouteHandler

func NewClientRouteHandler(svc clientroute.Service) *ClientRouteHandler

NewClientRouteHandler creates a new client route handler.

func (*ClientRouteHandler) HandleCreate

func (h *ClientRouteHandler) HandleCreate(w http.ResponseWriter, r *http.Request)

HandleCreate creates a new client route. POST /api/client-routes/create Body: ClientRoute JSON

@Summary		Create client route
@Tags			client-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ClientRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/client-routes/create [post]

func (*ClientRouteHandler) HandleDelete

func (h *ClientRouteHandler) HandleDelete(w http.ResponseWriter, r *http.Request)

HandleDelete deletes a client route. POST /api/client-routes/delete?id=xxx

@Summary		Delete client route
@Tags			client-routes
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Route id"
@Success		200	{object}	ClientRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/client-routes/delete [post]

func (*ClientRouteHandler) HandleList

func (h *ClientRouteHandler) HandleList(w http.ResponseWriter, r *http.Request)

HandleList returns all client routes. GET /api/client-routes

@Summary		List client routes
@Tags			client-routes
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ClientRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/client-routes [get]
@Router			/routing/client-routes [get]

func (*ClientRouteHandler) HandleToggle

func (h *ClientRouteHandler) HandleToggle(w http.ResponseWriter, r *http.Request)

HandleToggle enables or disables a client route. POST /api/client-routes/toggle?id=xxx Body: {"enabled": bool}

@Summary		Toggle client route
@Tags			client-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Route id"
@Success		200	{object}	ClientRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/client-routes/toggle [post]

func (*ClientRouteHandler) HandleUpdate

func (h *ClientRouteHandler) HandleUpdate(w http.ResponseWriter, r *http.Request)

HandleUpdate updates an existing client route. POST /api/client-routes/update?id=xxx Body: ClientRoute JSON

@Summary		Update client route
@Tags			client-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Route id"
@Success		200	{object}	ClientRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/client-routes/update [post]

func (*ClientRouteHandler) SetEventBus

func (h *ClientRouteHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus for SSE publishing.

type ClientRoutesListResponse

type ClientRoutesListResponse struct {
	Success bool             `json:"success" example:"true"`
	Data    []ClientRouteDTO `json:"data"`
}

ClientRoutesListResponse is the envelope for GET /client-routes.

type ConnectionProtocolsDTO

type ConnectionProtocolsDTO struct {
	TCP  int `json:"tcp" example:"28"`
	UDP  int `json:"udp" example:"12"`
	ICMP int `json:"icmp" example:"2"`
}

ConnectionProtocolsDTO holds per-protocol connection counts.

type ConnectionStatsDTO

type ConnectionStatsDTO struct {
	Total     int                    `json:"total" example:"42"`
	Direct    int                    `json:"direct" example:"30"`
	Tunneled  int                    `json:"tunneled" example:"12"`
	Protocols ConnectionProtocolsDTO `json:"protocols"`
}

ConnectionStatsDTO mirrors frontend ConnectionStats.

type ConnectionsData

type ConnectionsData struct {
	Stats ConnectionStatsDTO `json:"stats"`
	// Tunnels: per-tunnel counts; key "" is direct traffic (same as query tunnel=direct / UI Direct chip).
	Tunnels     map[string]TunnelConnectionInfoDTO `json:"tunnels"`
	Connections []ConntrackConnectionDTO           `json:"connections"`
	Pagination  ConnectionsPaginationDTO           `json:"pagination"`
	FetchedAt   string                             `json:"fetchedAt" example:"2024-01-15T10:30:00Z"`
}

ConnectionsData mirrors frontend ConnectionsResponse.

type ConnectionsHandler

type ConnectionsHandler struct {
	// contains filtered or unexported fields
}

ConnectionsHandler handles GET /api/connections.

func NewConnectionsHandler

func NewConnectionsHandler(svc *connections.Service) *ConnectionsHandler

NewConnectionsHandler creates a new connections handler.

func (*ConnectionsHandler) List

List returns filtered and paginated conntrack connections.

@Summary		Connections list
@Tags			connections
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ConnectionsResponseEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/connections [get]

type ConnectionsPaginationDTO

type ConnectionsPaginationDTO struct {
	Total    int `json:"total" example:"42"`
	Offset   int `json:"offset" example:"0"`
	Limit    int `json:"limit" example:"50"`
	Returned int `json:"returned" example:"42"`
}

ConnectionsPaginationDTO mirrors frontend ConnectionsPagination.

type ConnectionsResponseEnvelope

type ConnectionsResponseEnvelope struct {
	Success bool            `json:"success" example:"true"`
	Data    ConnectionsData `json:"data"`
}

ConnectionsResponseEnvelope is the envelope for GET /connections.

type ConnectivityResultData

type ConnectivityResultData struct {
	Connected bool   `json:"connected" example:"true"`
	Latency   int    `json:"latency,omitempty" example:"42"`
	Reason    string `json:"reason,omitempty" example:""`
}

ConnectivityResultData mirrors frontend ConnectivityResult.

type ConnectivityResultResponse

type ConnectivityResultResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    ConnectivityResultData `json:"data"`
}

ConnectivityResultResponse is the envelope for GET /test/connectivity.

type ConntrackConnectionDTO

type ConntrackConnectionDTO struct {
	Protocol   string `json:"protocol" example:"tcp"`
	Src        string `json:"src" example:"192.168.1.100"`
	Dst        string `json:"dst" example:"8.8.8.8"`
	SrcPort    int    `json:"srcPort" example:"54321"`
	DstPort    int    `json:"dstPort" example:"443"`
	State      string `json:"state" example:"ESTABLISHED"`
	Packets    int    `json:"packets" example:"15"`
	Bytes      int    `json:"bytes" example:"4096"`
	Interface  string `json:"interface" example:"nwg0"`
	TunnelId   string `json:"tunnelId" example:"tun_abc123"`
	TunnelName string `json:"tunnelName" example:"My VPN"`
	ClientMac  string `json:"clientMac" example:"aa:bb:cc:dd:ee:ff"`
	ClientName string `json:"clientName" example:"My Phone"`
}

ConntrackConnectionDTO mirrors frontend ConntrackConnection.

type ControlHandler

type ControlHandler struct {
	// contains filtered or unexported fields
}

ControlHandler handles tunnel start/stop/restart operations.

func NewControlHandler

func NewControlHandler(svc TunnelService, appLogger logging.AppLogger) *ControlHandler

NewControlHandler creates a new control handler.

func (*ControlHandler) Restart

func (h *ControlHandler) Restart(w http.ResponseWriter, r *http.Request)

Restart restarts a tunnel.

@Summary		Restart tunnel
@Tags			control
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	TunnelControlResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/control/restart [post]

func (*ControlHandler) RestartAll

func (h *ControlHandler) RestartAll(w http.ResponseWriter, r *http.Request)

RestartAll restarts all enabled tunnels.

@Summary		Restart all enabled tunnels
@Tags			control
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/control/restart-all [post]

func (*ControlHandler) SetCatalog

func (h *ControlHandler) SetCatalog(_ routing.Catalog)

SetCatalog is a no-op retained for API compatibility. The control handler no longer needs direct catalog access — it just emits a resource:invalidated hint so the polling store refetches.

func (*ControlHandler) SetEventBus

func (h *ControlHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus for SSE publishing.

func (*ControlHandler) SetOrchestrator

func (h *ControlHandler) SetOrchestrator(orch *orchestrator.Orchestrator)

SetOrchestrator sets the orchestrator for lifecycle operations.

func (*ControlHandler) SetPingCheckService

func (h *ControlHandler) SetPingCheckService(svc PingCheckService)

SetPingCheckService sets the ping check service for monitoring control.

func (*ControlHandler) SetTunnelsHandler

func (h *ControlHandler) SetTunnelsHandler(th *TunnelsHandler)

SetTunnelsHandler sets the tunnels handler for SSE list publishing.

func (*ControlHandler) Start

func (h *ControlHandler) Start(w http.ResponseWriter, r *http.Request)

Start starts a tunnel.

@Summary		Start tunnel
@Tags			control
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	TunnelControlResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/control/start [post]

func (*ControlHandler) Stop

Stop stops a tunnel.

@Summary		Stop tunnel
@Tags			control
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	TunnelControlResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/control/stop [post]

func (*ControlHandler) ToggleDefaultRoute

func (h *ControlHandler) ToggleDefaultRoute(w http.ResponseWriter, r *http.Request)

ToggleDefaultRoute toggles the default route setting for a tunnel.

@Summary		Toggle default route
@Tags			control
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/control/toggle-default-route [post]

func (*ControlHandler) ToggleEnabled

func (h *ControlHandler) ToggleEnabled(w http.ResponseWriter, r *http.Request)

ToggleEnabled toggles the auto-start setting for a tunnel.

@Summary		Toggle tunnel autostart
@Tags			control
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/control/toggle-enabled [post]

type CreateAccessPolicyRequest

type CreateAccessPolicyRequest struct {
	Description string `json:"description" example:"My VPN policy"`
}

CreateAccessPolicyRequest is the body for POST /access-policies/create.

type CreateServerRequestDTO

type CreateServerRequestDTO struct {
	Address     string `json:"address" example:"10.10.0.1"`
	Mask        string `json:"mask" example:"255.255.255.0"`
	ListenPort  int    `json:"listenPort" example:"51821"`
	Description string `json:"description,omitempty" example:"My WG Server"`
	Endpoint    string `json:"endpoint,omitempty" example:"203.0.113.42:51821"`
	DNS         string `json:"dns,omitempty" example:"8.8.8.8"`
	MTU         int    `json:"mtu,omitempty" example:"1420"`
	GenerateASC *bool  `json:"generateAsc,omitempty" example:"true"`
}

CreateServerRequestDTO is the swagger-visible body for POST /managed-servers.

type CreateSubscriptionRequest

type CreateSubscriptionRequest struct {
	Label        string                  `json:"label" example:"Demo Provider"`
	URL          string                  `json:"url,omitempty" example:"https://example.com/subscriptions/demo.txt"`
	Inline       string                  `` /* 256-byte string literal not displayed */
	Headers      []SubscriptionHeader    `json:"headers"`
	RefreshHours int                     `json:"refreshHours" example:"24"`
	Enabled      bool                    `json:"enabled" example:"true"`
	Mode         string                  `json:"mode,omitempty"` // "selector" (default) | "urltest"
	URLTest      *SubscriptionURLTestDTO `json:"urlTest,omitempty"`
	ExcludedKeys []string                `json:"excludedKeys,omitempty"` // identity-суффиксы серверов, снятых в import-preview
}

CreateSubscriptionRequest is the body for POST /api/singbox/subscriptions/create. Exactly one of URL or Inline must be provided.

type DNSRewritesHandler

type DNSRewritesHandler struct {
	// contains filtered or unexported fields
}

func NewDNSRewritesHandler

func NewDNSRewritesHandler(svc DNSRewritesService) *DNSRewritesHandler

func (*DNSRewritesHandler) Add

Add registers a new DNS rewrite.

@Summary		Add singbox-router DNS rewrite
@Description	Appends a new DNS rewrite (glob pattern → IPs). The pattern must be unique.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRewriteDTO	true	"DNS rewrite descriptor"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rewrites/add [post]

func (*DNSRewritesHandler) Delete

Delete removes the DNS rewrite at the given index.

@Summary		Delete singbox-router DNS rewrite
@Description	Removes the DNS rewrite at the given index (0-based priority slot).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRewriteDeleteRequest	true	"Index of the rewrite to remove"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rewrites/delete [post]

func (*DNSRewritesHandler) List

List returns all DNS rewrites in priority order.

@Summary		List singbox-router DNS rewrites
@Description	Returns all DNS rewrites (glob pattern → IPs) in priority order. Always a JSON array, never null.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxDNSRewritesListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rewrites/list [get]

func (*DNSRewritesHandler) Move

Move reorders a DNS rewrite from one priority slot to another.

@Summary		Move singbox-router DNS rewrite
@Description	Moves the DNS rewrite from index `from` to index `to` (both 0-based).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRewriteMoveRequest	true	"from/to indices"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rewrites/move [post]

func (*DNSRewritesHandler) Update

Update replaces the DNS rewrite at the given index.

@Summary		Update singbox-router DNS rewrite
@Description	Replaces the DNS rewrite at the given index (0-based priority slot) with the provided one.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRewriteUpdateRequest	true	"Index + replacement rewrite"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rewrites/update [post]

type DNSRewritesService

type DNSRewritesService interface {
	List() ([]dnsrewrite.DNSRewrite, error)
	Add(dnsrewrite.DNSRewrite) error
	Update(int, dnsrewrite.DNSRewrite) error
	Delete(int) error
	Move(from, to int) error
}

type DNSRouteHandler

type DNSRouteHandler struct {
	// contains filtered or unexported fields
}

DNSRouteHandler handles DNS route API endpoints.

func NewDNSRouteHandler

func NewDNSRouteHandler(svc DNSRouteService, appLogger logging.AppLogger) *DNSRouteHandler

NewDNSRouteHandler creates a new DNS route handler.

func (*DNSRouteHandler) BulkBackend

func (h *DNSRouteHandler) BulkBackend(w http.ResponseWriter, r *http.Request)

BulkBackend switches the routing backend for multiple lists.

@Summary		Bulk set DNS route backend
@Tags			dns-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/bulk-backend [post]

func (*DNSRouteHandler) Create

func (h *DNSRouteHandler) Create(w http.ResponseWriter, r *http.Request)

Create creates a new domain list.

@Summary		Create DNS route list
@Tags			dns-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		DnsRouteUpsertRequest	true	"DNS route list payload"
@Success		200	{object}	DnsRouteResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/create [post]

func (*DNSRouteHandler) CreateBatch

func (h *DNSRouteHandler) CreateBatch(w http.ResponseWriter, r *http.Request)

CreateBatch creates multiple domain lists at once.

@Summary		Create DNS route lists (batch)
@Tags			dns-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body	[]DnsRouteUpsertRequest	true	"DNS route list payloads"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/create-batch [post]

func (*DNSRouteHandler) Delete

func (h *DNSRouteHandler) Delete(w http.ResponseWriter, r *http.Request)

Delete deletes a domain list by ID and returns the fresh list so the client can call applyMutationResponse without a separate refetch.

@Summary		Delete DNS route list
@Tags			dns-routes
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"List id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/delete [post]

func (*DNSRouteHandler) DeleteBatch

func (h *DNSRouteHandler) DeleteBatch(w http.ResponseWriter, r *http.Request)

DeleteBatch deletes multiple domain lists by IDs.

@Summary		Delete DNS route lists (batch)
@Tags			dns-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body	DnsRouteDeleteBatchRequest	true	"IDs to delete"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/delete-batch [post]

func (*DNSRouteHandler) Get

Get returns a single domain list by ID.

@Summary		Get DNS route list
@Tags			dns-routes
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"List id"
@Success		200	{object}	DnsRouteResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/get [get]

func (*DNSRouteHandler) List

List returns all domain lists.

@Summary		List DNS route lists
@Tags			dns-routes
@Produce		json
@Security		CookieAuth
@Success		200	{object}	DnsRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/list [get]
@Router			/routing/dns-routes [get]

func (*DNSRouteHandler) Refresh

func (h *DNSRouteHandler) Refresh(w http.ResponseWriter, r *http.Request)

Refresh refreshes subscriptions for a single list or all lists.

@Summary		Refresh DNS route subscriptions
@Tags			dns-routes
@Produce		json
@Security		CookieAuth
@Param			id	query	string	false	"List id (omit for all)"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/refresh [post]

func (*DNSRouteHandler) SetEnabled

func (h *DNSRouteHandler) SetEnabled(w http.ResponseWriter, r *http.Request)

SetEnabled toggles the enabled state of a domain list.

@Summary		Set DNS route list enabled
@Tags			dns-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"List id"
@Param			body	body	EnabledToggleRequest	true	"Enabled flag"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/set-enabled [post]

func (*DNSRouteHandler) SetEventBus

func (h *DNSRouteHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus for SSE publishing.

func (*DNSRouteHandler) Update

func (h *DNSRouteHandler) Update(w http.ResponseWriter, r *http.Request)

Update updates an existing domain list.

@Summary		Update DNS route list
@Tags			dns-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"List id"
@Param			body	body	DnsRouteUpsertRequest	true	"DNS route list payload"
@Success		200	{object}	DnsRouteResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-routes/update [post]

type DNSRouteService

type DNSRouteService interface {
	Create(ctx context.Context, list dnsroute.DomainList) (*dnsroute.DomainList, error)
	Get(ctx context.Context, id string) (*dnsroute.DomainList, error)
	List(ctx context.Context) ([]dnsroute.DomainList, error)
	Update(ctx context.Context, list dnsroute.DomainList) (*dnsroute.DomainList, error)
	Delete(ctx context.Context, id string) error
	DeleteBatch(ctx context.Context, ids []string) (int, error)
	CreateBatch(ctx context.Context, lists []dnsroute.DomainList) ([]*dnsroute.DomainList, error)
	SetEnabled(ctx context.Context, id string, enabled bool) error
	RefreshSubscriptions(ctx context.Context, id string) error
	RefreshAllSubscriptions(ctx context.Context) error
}

DNSRouteService defines what the DNS route handler needs from the service.

type DNSRouteSettingsDTO

type DNSRouteSettingsDTO struct {
	AutoRefreshEnabled   bool   `json:"autoRefreshEnabled" example:"true"`
	RefreshIntervalHours int    `json:"refreshIntervalHours" example:"24"`
	RefreshMode          string `json:"refreshMode" example:"interval"`
	RefreshDailyTime     string `json:"refreshDailyTime" example:"03:00"`
}

DNSRouteSettingsDTO mirrors frontend DNSRouteSettings.

type DeviceProxyAuthDTO

type DeviceProxyAuthDTO struct {
	Enabled  bool   `json:"enabled" example:"false"`
	Username string `json:"username" example:""`
	Password string `json:"password" example:""`
}

DeviceProxyAuthDTO mirrors frontend DeviceProxyAuth.

type DeviceProxyConfigData

type DeviceProxyConfigData struct {
	Enabled          bool               `json:"enabled" example:"false"`
	ListenAll        bool               `json:"listenAll" example:"true"`
	ListenInterface  string             `json:"listenInterface" example:"br0"`
	Port             int                `json:"port" example:"1080"`
	Auth             DeviceProxyAuthDTO `json:"auth"`
	SelectedOutbound string             `json:"selectedOutbound" example:"proxy-01"`
}

DeviceProxyConfigData mirrors frontend DeviceProxyConfig.

type DeviceProxyHandler

type DeviceProxyHandler struct {
	// contains filtered or unexported fields
}

DeviceProxyHandler handles /api/proxy/* endpoints.

func NewDeviceProxyHandler

func NewDeviceProxyHandler(svc *deviceproxy.Service, appLogger logging.AppLogger) *DeviceProxyHandler

NewDeviceProxyHandler wires a DeviceProxyHandler with the given service and logger.

func (*DeviceProxyHandler) ApplyInstances

func (h *DeviceProxyHandler) ApplyInstances(w http.ResponseWriter, r *http.Request)

ApplyInstances handles POST /api/proxy/instances/apply.

@Summary		Apply all device proxy instances
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/instances/apply [post]

func (*DeviceProxyHandler) CheckInstanceExternalIP

func (h *DeviceProxyHandler) CheckInstanceExternalIP(w http.ResponseWriter, r *http.Request)

CheckInstanceExternalIP handles GET /api/proxy/instance/check-ip?id=...

@Summary		Check external IP through one device proxy instance
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Param			id		query		string	true	"Instance ID"
@Param			service	query		string	false	"Specific IP service URL"
@Success		200		{object}	DeviceProxyInstanceIPCheckResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/proxy/instance/check-ip [get]

func (*DeviceProxyHandler) DeleteInstance

func (h *DeviceProxyHandler) DeleteInstance(w http.ResponseWriter, r *http.Request)

DeleteInstance handles DELETE /api/proxy/instance?id=...

@Summary		Delete one device proxy instance
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/instance [delete]

func (*DeviceProxyHandler) ForceApply

func (h *DeviceProxyHandler) ForceApply(w http.ResponseWriter, r *http.Request)

ForceApply — POST /api/proxy/apply

Forces a full sing-box reload with the currently-persisted Config, bypassing the smart-reload diff in SaveConfig. Used by the UI "Применить сейчас" affordance when the user saved a new default via the no-reload surgical path and now wants the live selector to snap to that default.

@Summary		Force apply device proxy
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/apply [post]

func (*DeviceProxyHandler) GetConfig

func (h *DeviceProxyHandler) GetConfig(w http.ResponseWriter, r *http.Request)

GetConfig handles GET /api/proxy/config.

@Summary		Get device proxy config
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyConfigResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/config [get]

func (*DeviceProxyHandler) GetInstance

func (h *DeviceProxyHandler) GetInstance(w http.ResponseWriter, r *http.Request)

GetInstance handles GET /api/proxy/instance?id=...

@Summary		Get one device proxy instance
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyInstanceResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		404	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/instance [get]

func (*DeviceProxyHandler) GetInstanceRuntime

func (h *DeviceProxyHandler) GetInstanceRuntime(w http.ResponseWriter, r *http.Request)

GetInstanceRuntime handles GET /api/proxy/instance/runtime?id=...

@Summary		Get device proxy instance runtime state
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyRuntimeResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		404	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/instance/runtime [get]

func (*DeviceProxyHandler) GetRuntime

func (h *DeviceProxyHandler) GetRuntime(w http.ResponseWriter, r *http.Request)

GetRuntime — GET /api/proxy/runtime

@Summary		Device proxy runtime state
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyRuntimeResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/runtime [get]

func (*DeviceProxyHandler) ListInstances

func (h *DeviceProxyHandler) ListInstances(w http.ResponseWriter, r *http.Request)

ListInstances handles GET /api/proxy/instances.

@Summary		List device proxy instances
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyInstancesResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/instances [get]

func (*DeviceProxyHandler) ListOutbounds

func (h *DeviceProxyHandler) ListOutbounds(w http.ResponseWriter, r *http.Request)

ListOutbounds handles GET /api/proxy/outbounds.

@Summary		List device proxy outbounds
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyOutboundsResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/outbounds [get]

func (*DeviceProxyHandler) ListenChoices

func (h *DeviceProxyHandler) ListenChoices(w http.ResponseWriter, r *http.Request)

ListenChoices handles GET /api/proxy/listen-choices. Returns the bridge interface list, LAN IP, and singbox-running status needed by the frontend inbound settings form.

@Summary		Device proxy listen choices
@Tags			device-proxy
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyListenChoicesResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/listen-choices [get]

func (*DeviceProxyHandler) SaveConfig

func (h *DeviceProxyHandler) SaveConfig(w http.ResponseWriter, r *http.Request)

SaveConfig handles PUT /api/proxy/config.

@Summary		Save device proxy config
@Tags			device-proxy
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyConfigResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/config [put]

func (*DeviceProxyHandler) SaveInstance

func (h *DeviceProxyHandler) SaveInstance(w http.ResponseWriter, r *http.Request)

SaveInstance handles PUT /api/proxy/instance.

@Summary		Save one device proxy instance
@Tags			device-proxy
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyInstanceResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/instance [put]

func (*DeviceProxyHandler) SelectInstanceRuntime

func (h *DeviceProxyHandler) SelectInstanceRuntime(w http.ResponseWriter, r *http.Request)

SelectInstanceRuntime handles POST /api/proxy/instance/runtime/select.

@Summary		Select device proxy instance outbound
@Tags			device-proxy
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyRuntimeResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		404	{object}	APIErrorEnvelope
@Failure		409	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/instance/runtime/select [post]

func (*DeviceProxyHandler) SelectRuntime

func (h *DeviceProxyHandler) SelectRuntime(w http.ResponseWriter, r *http.Request)

SelectRuntime — POST /api/proxy/runtime/select body {"tag":"..."}

@Summary		Select device proxy outbound
@Tags			device-proxy
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ProxyRuntimeResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/proxy/runtime/select [post]

type DeviceProxyInstanceData

type DeviceProxyInstanceData struct {
	ID               string             `json:"id" example:"default"`
	Name             string             `json:"name" example:"Прокси"`
	Enabled          bool               `json:"enabled" example:"false"`
	ListenAll        bool               `json:"listenAll" example:"true"`
	ListenInterface  string             `json:"listenInterface" example:"br0"`
	Port             int                `json:"port" example:"1099"`
	Auth             DeviceProxyAuthDTO `json:"auth"`
	SelectedOutbound string             `json:"selectedOutbound" example:"proxy-01"`
}

DeviceProxyInstanceData mirrors deviceproxy.Instance for multi-instance API.

type DeviceProxyInstanceIPCheckResponse

type DeviceProxyInstanceIPCheckResponse struct {
	Success bool                                `json:"success" example:"true"`
	Data    DeviceProxyInstanceIPCheckResultDTO `json:"data"`
}

DeviceProxyInstanceIPCheckResponse is the envelope for GET /proxy/instance/check-ip.

type DeviceProxyInstanceIPCheckResultDTO

type DeviceProxyInstanceIPCheckResultDTO struct {
	DirectIP  string `json:"directIp" example:"203.0.113.1"`
	ProxyIP   string `json:"proxyIp" example:"198.51.100.42"`
	IPChanged bool   `json:"ipChanged" example:"false"`
	Service   string `json:"service" example:"https://api.ipify.org"`
}

DeviceProxyInstanceIPCheckResultDTO mirrors frontend DeviceProxyInstanceIPCheckResult and deviceproxy.InstanceIPCheckResult (OpenAPI / swag only sees types in internal/api).

type DeviceProxyOutboundDTO

type DeviceProxyOutboundDTO struct {
	Tag    string `json:"tag" example:"proxy-01"`
	Kind   string `json:"kind" example:"singbox"`
	Label  string `json:"label" example:"proxy-01 (VLESS)"`
	Detail string `json:"detail" example:"proxy.example.com:443"`
}

DeviceProxyOutboundDTO mirrors frontend DeviceProxyOutbound.

type DeviceProxyRuntimeData

type DeviceProxyRuntimeData struct {
	Alive      bool   `json:"alive" example:"true"`
	ActiveTag  string `json:"activeTag" example:"proxy-01"`
	DefaultTag string `json:"defaultTag" example:"proxy-01"`
}

DeviceProxyRuntimeData mirrors frontend DeviceProxyRuntime.

type DiagnosticsHandler

type DiagnosticsHandler struct {
	// contains filtered or unexported fields
}

DiagnosticsHandler handles diagnostics API endpoints.

func NewDiagnosticsHandler

func NewDiagnosticsHandler(runner DiagnosticsRunner) *DiagnosticsHandler

NewDiagnosticsHandler creates a new diagnostics handler.

func (*DiagnosticsHandler) Result

Result returns the last completed diagnostics report as a JSON file download. GET /api/diagnostics/result

@Summary		Download diagnostics report
@Tags			diagnostics
@Produce		application/json
@Security		CookieAuth
@Success		200	{file}	binary
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/diagnostics/result [get]

func (*DiagnosticsHandler) Run

Run starts a background diagnostic run. POST /api/diagnostics/run

@Summary		Run diagnostics
@Tags			diagnostics
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/diagnostics/run [post]

func (*DiagnosticsHandler) Status

Status returns the current diagnostic run status. GET /api/diagnostics/status

@Summary		Diagnostics status
@Tags			diagnostics
@Produce		json
@Security		CookieAuth
@Success		200	{object}	DiagnosticsStatusResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/diagnostics/status [get]

func (*DiagnosticsHandler) Stream

Stream starts a diagnostic run and streams results via SSE. GET /api/diagnostics/stream?restart=false Legacy `mode`, `route`, `tunnelId` query params are silently ignored for back-compat with old clients.

@Summary		Diagnostics SSE stream
@Tags			diagnostics
@Produce		text/event-stream
@Security		CookieAuth
@Success		200	{string}	string	"SSE stream"
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/diagnostics/stream [get]

type DiagnosticsRunner

type DiagnosticsRunner interface {
	Run(ctx context.Context) error
	RunWithStream(ctx context.Context, opts diagnostics.RunOptions) (<-chan diagnostics.DiagEvent, error)
	Status() diagnostics.RunStatus
	Result() ([]byte, error)
}

DiagnosticsRunner is the interface for running diagnostics.

type DiagnosticsStatusData

type DiagnosticsStatusData struct {
	Status   string `json:"status" example:"idle"`
	Progress string `json:"progress" example:""`
}

DiagnosticsStatusData mirrors frontend DiagnosticsStatus.

type DiagnosticsStatusResponse

type DiagnosticsStatusResponse struct {
	Success bool                  `json:"success" example:"true"`
	Data    DiagnosticsStatusData `json:"data"`
}

DiagnosticsStatusResponse is the envelope for GET /diagnostics/status.

type DnsCheckHandler

type DnsCheckHandler struct {
	// contains filtered or unexported fields
}

func NewDnsCheckHandler

func NewDnsCheckHandler(svc *dnscheck.Service) *DnsCheckHandler

func (*DnsCheckHandler) Client

func (h *DnsCheckHandler) Client(w http.ResponseWriter, r *http.Request)

Client returns client IP, hostname, and access-policy assignment only (fast path).

@Summary		Client context for diagnostics
@Tags			dns-check
@Produce		json
@Security		CookieAuth
@Success		200	{object}	DnsCheckStartResponseEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-check/client [get]

func (*DnsCheckHandler) Probe

Probe — cross-origin endpoint hit by the client's DNS probe fetch. If the client's DNS resolves awgm-dnscheck.test to the router, this endpoint is reachable and responds with 200. NO auth required.

@Summary		DNS check probe (CORS)
@Tags			dns-check
@Produce		json
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-check/probe [get]

func (*DnsCheckHandler) Start

Start initiates DNS diagnostic check (server-side checks only).

@Summary		Start DNS check
@Tags			dns-check
@Produce		json
@Security		CookieAuth
@Success		200	{object}	DnsCheckStartResponseEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/dns-check/start [post]

type DnsCheckResultDTO

type DnsCheckResultDTO struct {
	ID      string `json:"id" example:"dns_leak"`
	Status  string `json:"status" example:"ok"`
	Title   string `json:"title" example:"DNS Leak Test"`
	Message string `json:"message" example:"No DNS leak detected"`
	Detail  string `json:"detail,omitempty" example:""`
}

DnsCheckResultDTO mirrors frontend DnsCheckResult.

type DnsCheckStartData

type DnsCheckStartData struct {
	ClientIP string              `json:"clientIP" example:"192.168.1.100"`
	Hostname string              `json:"hostname" example:"my-phone.local"`
	Checks   []DnsCheckResultDTO `json:"checks"`
}

DnsCheckStartData mirrors frontend DnsCheckStartResponse.

type DnsCheckStartResponseEnvelope

type DnsCheckStartResponseEnvelope struct {
	Success bool              `json:"success" example:"true"`
	Data    DnsCheckStartData `json:"data"`
}

DnsCheckStartResponseEnvelope is the envelope for POST /dns-check/start.

type DnsProxyInfoData

type DnsProxyInfoData struct {
	Proxies []diagnostics.DNSProxy `json:"proxies"`
}

DnsProxyInfoData is the response payload.

type DnsProxyInfoEnvelope

type DnsProxyInfoEnvelope struct {
	Success bool             `json:"success" example:"true"`
	Data    DnsProxyInfoData `json:"data"`
}

DnsProxyInfoEnvelope documents the success envelope for swagger.

type DnsProxyInfoHandler

type DnsProxyInfoHandler struct {
	// contains filtered or unexported fields
}

func NewDnsProxyInfoHandler

func NewDnsProxyInfoHandler(store dnsProxyStatusReader, policies accessPolicyLister) *DnsProxyInfoHandler

func (*DnsProxyInfoHandler) Get

Get returns the parsed /show/dns-proxy state.

@Summary		DNS proxy info
@Description	Running ndnproxy state: upstreams, per-policy stats, static records, rebind.
@Tags			diagnostics
@Produce		json
@Security		CookieAuth
@Success		200	{object}	DnsProxyInfoEnvelope
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/diagnostics/dns-proxy [get]

type DnsRouteDTO

type DnsRouteDTO struct {
	ID                 string                    `json:"id" example:"dns_xyz789"`
	Name               string                    `json:"name" example:"Work VPN"`
	Domains            []string                  `json:"domains" example:"example.com"`
	Excludes           []string                  `json:"excludes,omitempty" example:"ads.example.com"`
	ExcludesText       string                    `json:"excludesText,omitempty" example:"# local bypass\n.local\n10.0.0.0/8"`
	ExcludeSubnets     []string                  `json:"excludeSubnets,omitempty" example:"10.0.0.0/24"`
	Subnets            []string                  `json:"subnets,omitempty" example:"10.0.0.0/8"`
	ManualDomains      []string                  `json:"manualDomains" example:"corp.internal"`
	ManualText         string                    `json:"manualText,omitempty" example:"# streaming\nyoutube.com\n# backup\ngooglevideo.com"`
	Subscriptions      []DnsRouteSubscriptionDTO `json:"subscriptions,omitempty"`
	Routes             []DnsRouteTargetDTO       `json:"routes"`
	Enabled            bool                      `json:"enabled" example:"true"`
	CreatedAt          string                    `json:"createdAt" example:"2024-01-01T00:00:00Z"`
	UpdatedAt          string                    `json:"updatedAt" example:"2024-01-15T12:00:00Z"`
	Backend            string                    `json:"backend,omitempty" enums:"ndms,hydraroute" example:"hydraroute"`
	IconURL            string                    `json:"iconUrl,omitempty" example:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."`
	HRRouteMode        string                    `json:"hrRouteMode,omitempty" enums:"interface,policy" example:"interface"`
	HRPolicyName       string                    `json:"hrPolicyName,omitempty" example:"Streaming"`
	HRPolicyInterfaces []string                  `json:"hrPolicyInterfaces,omitempty" example:"Wireguard0"`
}

DnsRouteDTO mirrors frontend DnsRoute.

type DnsRouteDeleteBatchRequest

type DnsRouteDeleteBatchRequest struct {
	IDs []string `json:"ids" example:"dns_a,dns_b"`
}

DnsRouteDeleteBatchRequest is the request body for batch delete.

type DnsRouteResponse

type DnsRouteResponse struct {
	Success bool        `json:"success" example:"true"`
	Data    DnsRouteDTO `json:"data"`
}

DnsRouteResponse is the envelope for GET /dns-routes/get and mutations.

type DnsRouteSubscriptionDTO

type DnsRouteSubscriptionDTO struct {
	URL         string `json:"url" example:"https://example.com/list.txt"`
	Name        string `json:"name" example:"Block list"`
	LastFetched string `json:"lastFetched,omitempty" example:"2024-01-15T02:00:00Z"`
	LastCount   int    `json:"lastCount,omitempty" example:"1500"`
}

DnsRouteSubscriptionDTO mirrors frontend DnsRouteSubscription.

type DnsRouteTargetDTO

type DnsRouteTargetDTO struct {
	Interface string `json:"interface" example:"nwg0"`
	TunnelId  string `json:"tunnelId" example:"tun_abc123"`
	Fallback  string `json:"fallback,omitempty" example:"auto"`
}

DnsRouteTargetDTO mirrors frontend DnsRouteTarget.

type DnsRouteUpsertRequest

type DnsRouteUpsertRequest struct {
	Name               string                    `json:"name" example:"Youtube"`
	ManualDomains      []string                  `json:"manualDomains" example:"youtube.com,.googlevideo.com,geosite:GOOGLE"`
	ManualText         string                    `json:"manualText,omitempty" example:"# game servers\nexample.com\n203.0.113.0/24"`
	Subscriptions      []DnsRouteSubscriptionDTO `json:"subscriptions,omitempty"`
	Excludes           []string                  `json:"excludes,omitempty" example:"ads.youtube.com"`
	ExcludesText       string                    `json:"excludesText,omitempty" example:"# local bypass\n.local\n10.0.0.0/8"`
	ExcludeSubnets     []string                  `json:"excludeSubnets,omitempty" example:"10.0.0.0/24"`
	Subnets            []string                  `json:"subnets,omitempty" example:"142.250.0.0/15"`
	Routes             []DnsRouteTargetDTO       `json:"routes,omitempty"`
	Enabled            bool                      `json:"enabled" example:"true"`
	Backend            string                    `json:"backend,omitempty" enums:"ndms,hydraroute" example:"hydraroute"`
	IconURL            string                    `json:"iconUrl,omitempty" example:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."`
	HRRouteMode        string                    `json:"hrRouteMode,omitempty" enums:"interface,policy" example:"interface"`
	HRPolicyName       string                    `json:"hrPolicyName,omitempty" example:"Streaming"`
	HRPolicyInterfaces []string                  `json:"hrPolicyInterfaces,omitempty" example:"Wireguard0"`
}

DnsRouteUpsertRequest is the request body for DNS/HR route list create/update.

type DnsRoutesListResponse

type DnsRoutesListResponse struct {
	Success bool          `json:"success" example:"true"`
	Data    []DnsRouteDTO `json:"data"`
}

DnsRoutesListResponse is the envelope for GET /dns-routes/list.

type DownloadHandler

type DownloadHandler struct {
	// contains filtered or unexported fields
}

func NewDownloadHandler

func NewDownloadHandler(svc *downloader.Service) *DownloadHandler

func (*DownloadHandler) ListOutbounds

func (h *DownloadHandler) ListOutbounds(w http.ResponseWriter, r *http.Request)

ListOutbounds returns existing route options for service downloads.

@Summary		List service download outbounds
@Tags			download
@Produce		json
@Security		CookieAuth
@Success		200	{object}	DownloadOutboundsResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/download/outbounds [get]

type DownloadOutboundDTO

type DownloadOutboundDTO struct {
	Tag       string `json:"tag" example:"direct"`
	Kind      string `json:"kind" example:"direct"`
	Label     string `json:"label" example:"Direct (WAN)"`
	Detail    string `json:"detail,omitempty" example:"без туннеля"`
	Available bool   `json:"available" example:"true"`
}

type DownloadOutboundsResponse

type DownloadOutboundsResponse struct {
	Success bool                  `json:"success" example:"true"`
	Data    []DownloadOutboundDTO `json:"data"`
}

type DownloadRouteDTO

type DownloadRouteDTO struct {
	Tag  string `json:"tag" example:"direct"`
	Kind string `json:"kind,omitempty" example:"direct"`
}

type DownloadSettingsDTO

type DownloadSettingsDTO struct {
	RouteTag  string `json:"routeTag" example:"direct"`
	RouteKind string `json:"routeKind,omitempty" example:"direct"`
}

type EnabledToggleRequest

type EnabledToggleRequest struct {
	Enabled bool `json:"enabled" example:"true"`
}

EnabledToggleRequest is the body for endpoints that flip an enabled flag (SetEnabled, TogglePeer).

type EventsHandler

type EventsHandler struct {
	// contains filtered or unexported fields
}

EventsHandler serves the SSE event stream.

func NewEventsHandler

func NewEventsHandler(bus *events.Bus, instanceID string) *EventsHandler

NewEventsHandler creates a new events handler.

func (*EventsHandler) Stream

func (h *EventsHandler) Stream(w http.ResponseWriter, r *http.Request)

Stream serves the SSE event stream. GET /api/events

@Summary		SSE event stream
@Tags			events
@Produce		text/event-stream
@Security		CookieAuth
@Success		200	{string}	string	"Server-Sent Events"
@Success		299	{object}	SingboxRouterTransitionData	"Schema of a singbox-router:transition push event carried on this stream (documentation only — never an HTTP status)"
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/events [get]

The stream carries only incremental/push-only events (traffic, connectivity, logs, ping-check logs, sing-box delay/traffic, geo download progress, DNS-route failover notifications, and the generic resource:invalidated hint). All cold-tier state is fetched via REST by the frontend polling stores; the initial "connected" marker lets the client confirm the stream is open before any push event arrives.

type ExcludeMembersRequest

type ExcludeMembersRequest struct {
	MemberTags []string `json:"memberTags" example:"sub-demo-002,sub-demo-003"`
}

ExcludeMembersRequest is the body for POST /api/singbox/subscriptions/members/exclude. Excluding members is only allowed on URL-backed subscriptions and is reversible via restore.

type ExternalTunnelDTO

type ExternalTunnelDTO struct {
	InterfaceName string `json:"interfaceName" example:"Wireguard2"`
	TunnelNumber  int    `json:"tunnelNumber" example:"2"`
	IsAWG         bool   `json:"isAWG" example:"true"`
	PublicKey     string `json:"publicKey,omitempty" example:"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK="`
	Endpoint      string `json:"endpoint,omitempty" example:"ext.example.com:51820"`
	RxBytes       int64  `json:"rxBytes" example:"1048576"`
	TxBytes       int64  `json:"txBytes" example:"524288"`
}

ExternalTunnelDTO mirrors frontend ExternalTunnel.

type ExternalTunnelService

type ExternalTunnelService interface {
	List(ctx context.Context) ([]external.TunnelInfo, error)
	Adopt(ctx context.Context, req external.AdoptRequest) (*service.TunnelWithStatus, error)
}

ExternalTunnelService defines the interface for external tunnel operations.

type ExternalTunnelsHandler

type ExternalTunnelsHandler struct {
	// contains filtered or unexported fields
}

ExternalTunnelsHandler handles external tunnel operations.

func NewExternalTunnelsHandler

func NewExternalTunnelsHandler(svc ExternalTunnelService, tunnelSvc TunnelService, store *storage.AWGTunnelStore, appLogger logging.AppLogger) *ExternalTunnelsHandler

NewExternalTunnelsHandler creates a new external tunnels handler.

func (*ExternalTunnelsHandler) Adopt

Adopt takes control of an external tunnel. Endpoint: POST /api/external-tunnels/adopt?interface=opkgtunX

@Summary		Adopt external tunnel
@Tags			tunnels
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			interface	query		string			true	"NDMS interface name"
@Param			body		body		AdoptRequest	true	"Tunnel config body"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/external-tunnels/adopt [post]

func (*ExternalTunnelsHandler) List

List returns all external (unmanaged) tunnels. Endpoint: GET /api/external-tunnels

@Summary		List external tunnels
@Tags			tunnels
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ExternalTunnelsResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/external-tunnels [get]

func (*ExternalTunnelsHandler) SetTunnelListPublisher

func (h *ExternalTunnelsHandler) SetTunnelListPublisher(fn func(ctx context.Context))

SetTunnelListPublisher sets the function that publishes the full tunnel list via SSE.

type ExternalTunnelsResponse

type ExternalTunnelsResponse struct {
	Success bool                `json:"success" example:"true"`
	Data    []ExternalTunnelDTO `json:"data"`
}

ExternalTunnelsResponse is the envelope for GET /external-tunnels.

type GeoExpandData

type GeoExpandData struct {
	Lines []string `json:"lines"`
	Path  string   `json:"path"`
	Count int      `json:"count"`
}

GeoExpandData is the payload for GET /hydraroute/geo-expand.

type GeoFileEntryDTO

type GeoFileEntryDTO struct {
	Type     string `json:"type" example:"geosite"`
	Path     string `json:"path" example:"/opt/etc/hrneo/geosite.db"`
	URL      string `json:"url" example:"https://cdn.example.com/geosite.db"`
	Size     int64  `json:"size" example:"3145728"`
	TagCount int    `json:"tagCount" example:"420"`
	Updated  string `json:"updated" example:"2024-01-15T02:00:00Z"`
	External bool   `json:"external,omitempty" example:"true"`
}

GeoFileEntryDTO mirrors frontend GeoFileEntry.

type GeoFileResponse

type GeoFileResponse struct {
	Success bool            `json:"success" example:"true"`
	Data    GeoFileEntryDTO `json:"data"`
}

GeoFileResponse is the envelope for endpoints that return a single geo file entry (POST /hydraroute/geo-files/add).

type GeoFileSettingsDTO

type GeoFileSettingsDTO struct {
	AutoRefreshEnabled   bool   `json:"autoRefreshEnabled" example:"false"`
	RefreshIntervalHours int    `json:"refreshIntervalHours" example:"24"`
	RefreshMode          string `json:"refreshMode" example:"interval"`
	RefreshDailyTime     string `json:"refreshDailyTime" example:"03:00"`
}

GeoFileSettingsDTO mirrors frontend GeoFileSettings.

type GeoFileUpdatedData

type GeoFileUpdatedData struct {
	Updated int    `json:"updated" example:"1"`
	Partial bool   `json:"partial,omitempty" example:"true"`
	Error   string `json:"error,omitempty"`
}

GeoFileUpdatedData reports how many geo files were re-downloaded by POST /hydraroute/geo-files/update. The shape is the same whether the caller targeted one path or all paths.

type GeoFileUpdatedResponse

type GeoFileUpdatedResponse struct {
	Success bool               `json:"success" example:"true"`
	Data    GeoFileUpdatedData `json:"data"`
}

GeoFileUpdatedResponse is the envelope for POST /hydraroute/geo-files/update.

type GeoFilesRescannedData

type GeoFilesRescannedData struct {
	Adopted int `json:"adopted" example:"1"`
}

GeoFilesRescannedData reports how many new paths were adopted from hrneo.conf.

type GeoFilesRescannedResponse

type GeoFilesRescannedResponse struct {
	Success bool                  `json:"success" example:"true"`
	Data    GeoFilesRescannedData `json:"data"`
}

GeoFilesRescannedResponse is the envelope for POST /hydraroute/geo-files/rescan.

type GeoFilesResponse

type GeoFilesResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    []GeoFileEntryDTO `json:"data"`
}

GeoFilesResponse is the envelope for GET /hydraroute/geo-files.

type GeoTagDTO

type GeoTagDTO struct {
	Name  string `json:"name" example:"google"`
	Count int    `json:"count" example:"1250"`
}

GeoTagDTO mirrors frontend GeoTag.

type GeoTagsResponse

type GeoTagsResponse struct {
	Success bool        `json:"success" example:"true"`
	Data    []GeoTagDTO `json:"data"`
}

GeoTagsResponse is the envelope for GET /hydraroute/geo-tags.

type HealthData

type HealthData struct {
	OK         bool   `json:"ok" example:"true"`
	Version    string `json:"version" example:"2.5.0"`
	InstanceID string `json:"instanceId" example:"b6c2ef8df6cf4b2782f8f45a7dc8e3a1"`
}

HealthData is the response data for GET /health.

type HealthHandler

type HealthHandler struct {
	// contains filtered or unexported fields
}

HealthHandler serves GET /api/health. A cheap liveness check that does no I/O, no NDMS calls — used by the frontend 5-second poller to decide when to show the full-screen "backend offline" overlay independently of SSE connection state.

func NewHealthHandler

func NewHealthHandler(version, instanceID string) *HealthHandler

NewHealthHandler constructs a HealthHandler that reports the given build version. The version is set via ldflags at build time and propagated from cmd/awg-manager/main.go through server.Config.Version.

func (*HealthHandler) ServeHTTP

func (h *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP responds to GET with { ok: true, version: "...", instanceId: "..." }. Any other method returns 405 Method Not Allowed.

@Summary		Health / liveness
@Description	Cheap check for frontend pollers; no NDMS or I/O.
@Tags			system
@Produce		json
@Success		200	{object}	HealthResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/health [get]

type HealthResponse

type HealthResponse struct {
	Success bool       `json:"success" example:"true"`
	Data    HealthData `json:"data"`
}

HealthResponse is the envelope for GET /health.

type HookDispatcher

type HookDispatcher interface {
	Enqueue(e events.Event)
}

HookDispatcher is the subset of events.Dispatcher that HookHandler uses. Interface so tests can inject a fake.

type HookHandler

type HookHandler struct {
	// contains filtered or unexported fields
}

HookHandler handles NDM hook events.

func NewHookHandler

func NewHookHandler(svc TunnelService, orch *orchestrator.Orchestrator, appLogger logging.AppLogger) *HookHandler

NewHookHandler creates a new hook event handler.

func (*HookHandler) EnterSelfCreate

func (h *HookHandler) EnterSelfCreate()

EnterSelfCreate marks the start of an awg-manager-initiated NDMS interface creation. Pair with ExitSelfCreate via defer.

func (*HookHandler) ExitSelfCreate

func (h *HookHandler) ExitSelfCreate()

ExitSelfCreate marks the end of an awg-manager-initiated NDMS interface creation. Callers MUST publish a fresh tunnels invalidation hint themselves after this (typically via TunnelsHandler.publishTunnelList) so UIs see the finalized state.

func (*HookHandler) HandleNDMS

func (h *HookHandler) HandleNDMS(w http.ResponseWriter, r *http.Request)

HandleNDMS is the unified hook endpoint. The shared forwarder script installed into /opt/etc/ndm/{iflayerchanged,ifcreated,ifdestroyed, ifipchanged}.d/ POSTs here with a `type` discriminator. The handler parses the form into a typed events.Event, enqueues it into the Dispatcher for cache invalidation, and (for iflayerchanged only) also forwards to the orchestrator for tunnel-lifecycle decisions.

POST /api/hook/ndms

  type=iflayerchanged|ifcreated|ifdestroyed|ifipchanged
  id=<ndms-interface-id>
  system_name=<kernel-name>
  layer=<conf|link|ipv4|ipv6|ctrl>      (layerchanged only)
  level=<running|disabled|...>          (layerchanged only)
  address=<ipv4>                        (ipchanged only)
  up=<0|1>
  connected=<0|1>

	@Summary		NDMS shell hook
	@Description	Called from router scripts (public). Form fields: type, id, system_name, layer, etc.
	@Tags			hook
	@Accept			x-www-form-urlencoded
	@Produce		json
	@Param			type	formData	string	true	"Event type (iflayerchanged, ifcreated, ...)"
	@Success		200	{object}	APIEnvelope
	@Failure		400	{object}	APIErrorEnvelope
	@Failure		500	{object}	APIErrorEnvelope
	@Router			/hook/ndms [post]

func (*HookHandler) SetDispatcher

func (h *HookHandler) SetDispatcher(d HookDispatcher)

SetDispatcher wires an events.Dispatcher for hook-driven cache invalidation. Call after construction (typically from server.New).

func (*HookHandler) SetTunnelRefresher

func (h *HookHandler) SetTunnelRefresher(fn TunnelHookInvalidator)

SetTunnelRefresher wires the callback that invalidates NDMS caches and publishes a tunnels `resource:invalidated` hint on ifcreated / ifdestroyed. Without it, the UI keeps showing cards for tunnels that NDMS has already torn down (reported bug).

func (*HookHandler) SetWANModel

func (h *HookHandler) SetWANModel(m HookWANModel)

SetWANModel wires the WAN model so iflayerchanged layer=ipv4 hooks can update WAN interface up/down state in-memory before dispatching EventWANUp/Down to the orchestrator.

type HookWANModel

type HookWANModel interface {
	SetUp(kernelName string, up bool)
}

HookWANModel is the narrow surface HookHandler needs from the WAN model. Kept local so api/hook.go doesn't depend on *wan.Model.

type HotspotLister

type HotspotLister interface {
	List(ctx context.Context) ([]ndms.Device, error)
}

HotspotLister is the narrow contract this handler needs from the NDMS hotspot cache. Lives here so the handler doesn't import internal/ndms/query directly — fake-friendly.

type HydraRouteConfigData

type HydraRouteConfigData struct {
	AutoStart          bool     `json:"autoStart" example:"true"`
	ClearIPSet         bool     `json:"clearIPSet" example:"false"`
	CIDR               bool     `json:"cidr" example:"true"`
	IpsetEnableTimeout bool     `json:"ipsetEnableTimeout" example:"false"`
	IpsetTimeout       int      `json:"ipsetTimeout" example:"0"`
	IpsetMaxElem       int      `json:"ipsetMaxElem" example:"65536"`
	DirectRouteEnabled bool     `json:"directRouteEnabled" example:"false"`
	GlobalRouting      bool     `json:"globalRouting" example:"false"`
	ConntrackFlush     bool     `json:"conntrackFlush" example:"true"`
	Log                string   `json:"log" example:"warn"`
	LogFile            string   `json:"logFile" example:"/var/log/hrneo.log"`
	GeoIPFiles         []string `json:"geoIPFiles" example:"/opt/etc/hrneo/geoip.db"`
	GeoSiteFiles       []string `json:"geoSiteFiles" example:"/opt/etc/hrneo/geosite.db"`
	PolicyOrder        []string `json:"policyOrder" example:"default"`
}

HydraRouteConfigData mirrors frontend HydraRouteConfig.

type HydraRouteConfigResponse

type HydraRouteConfigResponse struct {
	Success bool                 `json:"success" example:"true"`
	Data    HydraRouteConfigData `json:"data"`
}

HydraRouteConfigResponse is the envelope for GET /hydraroute/config.

type HydraRouteHandler

type HydraRouteHandler struct {
	// contains filtered or unexported fields
}

HydraRouteHandler handles HydraRoute Neo settings API endpoints.

func NewHydraRouteHandler

func NewHydraRouteHandler(svc *hydraroute.Service, downloadSvc *downloader.Service) *HydraRouteHandler

NewHydraRouteHandler creates a new HydraRoute settings handler.

func (*HydraRouteHandler) AddGeoFile

func (h *HydraRouteHandler) AddGeoFile(w http.ResponseWriter, r *http.Request)

AddGeoFile downloads and registers a new geo data file.

@Summary		Add HydraRoute geo file
@Tags			hydraroute
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		AddGeoFileRequest	true	"Geo file source descriptor"
@Success		200		{object}	GeoFileResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/hydraroute/geo-files/add [post]

func (*HydraRouteHandler) DeleteGeoFile

func (h *HydraRouteHandler) DeleteGeoFile(w http.ResponseWriter, r *http.Request)

DeleteGeoFile removes a tracked geo data file.

@Summary		Delete HydraRoute geo file
@Description	Removes the tracked geo data file at the given path and re-syncs the geo file list to config.
@Tags			hydraroute
@Produce		json
@Security		CookieAuth
@Param			path	query		string	true	"Filesystem path of the geo file"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/hydraroute/geo-files/delete [delete]

func (*HydraRouteHandler) ExpandGeoTag

func (h *HydraRouteHandler) ExpandGeoTag(w http.ResponseWriter, r *http.Request)

ExpandGeoTag expands a geosite:/geoip: tag into inline rule list lines.

@Summary		HydraRoute geo tag expand
@Tags			hydraroute
@Produce		json
@Security		CookieAuth
@Param			kind	query	string	true	"geosite or geoip"
@Param			tag	query	string	true	"Tag name"
@Success		200	{object}	GeoExpandData
@Router			/hydraroute/geo-expand [get]

func (*HydraRouteHandler) GetConfig

func (h *HydraRouteHandler) GetConfig(w http.ResponseWriter, r *http.Request)

GetConfig returns the current HydraRoute config.

@Summary		Get HydraRoute config
@Description	Available when HydraRoute service is wired on the device.
@Tags			hydraroute
@Produce		json
@Security		CookieAuth
@Success		200	{object}	HydraRouteConfigResponse
@Router			/hydraroute/config [get]

func (*HydraRouteHandler) GetGeoTags

func (h *HydraRouteHandler) GetGeoTags(w http.ResponseWriter, r *http.Request)

GetGeoTags returns the tag list for a specific geo data file.

@Summary		HydraRoute geo tags
@Tags			hydraroute
@Produce		json
@Security		CookieAuth
@Param			path	query	string	true	"Geo file path"
@Success		200	{array}	string
@Router			/hydraroute/geo-tags [get]

func (*HydraRouteHandler) GetIpsetUsage

func (h *HydraRouteHandler) GetIpsetUsage(w http.ResponseWriter, r *http.Request)

GetIpsetUsage returns the current ipset usage per kernel interface.

@Summary		HydraRoute ipset usage
@Tags			hydraroute
@Produce		json
@Security		CookieAuth
@Success		200	{object}	IpsetUsageResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/hydraroute/ipset-usage [get]

func (*HydraRouteHandler) GetOversizedTags

func (h *HydraRouteHandler) GetOversizedTags(w http.ResponseWriter, r *http.Request)

GetOversizedTags returns the list of geoip tags HR Neo excluded plus the current IpsetMaxElem so the frontend can render the 'Отключённые теги' pane and enforce picker limits.

@Summary		HydraRoute oversized geo tags
@Tags			hydraroute
@Produce		json
@Security		CookieAuth
@Success		200	{object}	OversizedTagsResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/hydraroute/oversized-tags [get]

func (*HydraRouteHandler) ListGeoFiles

func (h *HydraRouteHandler) ListGeoFiles(w http.ResponseWriter, r *http.Request)

ListGeoFiles returns all tracked geo data files.

@Summary		List HydraRoute geo files
@Tags			hydraroute
@Produce		json
@Security		CookieAuth
@Success		200	{object}	GeoFilesResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/hydraroute/geo-files [get]

func (*HydraRouteHandler) RescanGeoFiles

func (h *HydraRouteHandler) RescanGeoFiles(w http.ResponseWriter, r *http.Request)

RescanGeoFiles adopts geo paths from hrneo.conf not yet in the catalog.

@Summary		Rescan HydraRoute geo files from hrneo.conf
@Tags			hydraroute
@Produce		json
@Security		CookieAuth
@Success		200	{object}	GeoFilesRescannedResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/hydraroute/geo-files/rescan [post]

func (*HydraRouteHandler) SetEventBus

func (h *HydraRouteHandler) SetEventBus(bus *events.Bus)

SetEventBus wires the SSE bus so HR Neo mutations that touch the DNS route list (policy order, native rule import, config write) can emit resource:invalidated hints for `routing.dnsRoutes`, and so HR daemon state changes publish `routing.hydrarouteStatus` hints.

func (*HydraRouteHandler) SetPolicyOrder

func (h *HydraRouteHandler) SetPolicyOrder(w http.ResponseWriter, r *http.Request)

SetPolicyOrder updates the PolicyOrder in hrneo.conf.

@Summary		Set HydraRoute policy order
@Tags			hydraroute
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SetPolicyOrderRequest	true	"Ordered list of policy names"
@Success		200		{object}	PolicyOrderResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/hydraroute/policy-order [post]

func (*HydraRouteHandler) TakeGeoFileControl

func (h *HydraRouteHandler) TakeGeoFileControl(w http.ResponseWriter, r *http.Request)

TakeGeoFileControl moves an external HydraRoute file into awg-manager/geo.

@Summary		Take HydraRoute geo file under awg-manager control
@Tags			hydraroute
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		TakeGeoFileControlRequest	true	"Filesystem path of the external geo file"
@Success		200		{object}	GeoFileResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/hydraroute/geo-files/take-control [post]

func (*HydraRouteHandler) UpdateConfig

func (h *HydraRouteHandler) UpdateConfig(w http.ResponseWriter, r *http.Request)

UpdateConfig writes the HydraRoute config.

@Summary		Update HydraRoute config
@Description	Persists the HydraRoute (HrNeo) config and schedules a neo restart. The cached status becomes stale and is invalidated via SSE.
@Tags			hydraroute
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		HydraRouteConfigData	true	"hydraroute.Config"
@Success		200		{object}	HydraRouteConfigResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/hydraroute/config/update [put]

func (*HydraRouteHandler) UpdateGeoFile

func (h *HydraRouteHandler) UpdateGeoFile(w http.ResponseWriter, r *http.Request)

UpdateGeoFile re-downloads a geo data file (or all files if path is empty).

@Summary		Refresh HydraRoute geo file(s)
@Description	Empty path triggers a bulk refresh of every tracked geo file. Single path refreshes one file. Both branches return an updated count for caller-side feedback; the frontend refetches the list afterwards.
@Tags			hydraroute
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		UpdateGeoFileRequest	true	"Path to refresh, or empty to refresh all"
@Success		200		{object}	GeoFileUpdatedResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/hydraroute/geo-files/update [post]

type HydraRouteStatusData

type HydraRouteStatusData struct {
	Installed    bool   `json:"installed" example:"true"`
	Running      bool   `json:"running" example:"true"`
	Version      string `json:"version,omitempty" example:"2.4.1"`
	PID          int    `json:"pid,omitempty" example:"12345"`
	StalePID     int    `json:"stalePid,omitempty" example:"12345"`
	ProcessState string `json:"processState" example:"running" enums:"not_installed,stopped,running,dead"`
	LastError    string `json:"lastError,omitempty" example:"neo restart: exit status 1"`
}

HydraRouteStatusData mirrors frontend HydraRouteStatus.

type HydraRouteStatusResponse

type HydraRouteStatusResponse struct {
	Success bool                 `json:"success" example:"true"`
	Data    HydraRouteStatusData `json:"data"`
}

HydraRouteStatusResponse is the envelope for GET /system/hydraroute-status.

type IPCheckServiceDTO

type IPCheckServiceDTO struct {
	Label string `json:"label" example:"ipinfo.io"`
	URL   string `json:"url" example:"https://ipinfo.io/ip"`
}

IPCheckServiceDTO mirrors frontend IPCheckService.

type IPResultData

type IPResultData struct {
	DirectIp   string `json:"directIp" example:"203.0.113.1"`
	VpnIp      string `json:"vpnIp" example:"185.220.101.1"`
	EndpointIp string `json:"endpointIp" example:"203.0.113.42"`
	IpChanged  bool   `json:"ipChanged" example:"true"`
}

IPResultData mirrors frontend IPResult.

type IPResultResponse

type IPResultResponse struct {
	Success bool         `json:"success" example:"true"`
	Data    IPResultData `json:"data"`
}

IPResultResponse is the envelope for GET /test/ip.

type IPServicesResponse

type IPServicesResponse struct {
	Success bool                `json:"success" example:"true"`
	Data    []IPCheckServiceDTO `json:"data"`
}

IPServicesResponse is the envelope for GET /test/ip/services.

type ImportHandler

type ImportHandler struct {
	// contains filtered or unexported fields
}

ImportHandler handles config import operations.

func NewImportHandler

func NewImportHandler(svc TunnelService, store *storage.AWGTunnelStore, appLogger logging.AppLogger) *ImportHandler

NewImportHandler creates a new import handler.

func (*ImportHandler) ImportConf

func (h *ImportHandler) ImportConf(w http.ResponseWriter, r *http.Request)

ImportConf imports a WireGuard/AmneziaWG config file.

@Summary		Import tunnel config
@Tags			import
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/import/conf [post]

func (*ImportHandler) SetPingCheckService

func (h *ImportHandler) SetPingCheckService(svc PingCheckService)

SetPingCheckService sets the ping check service.

func (*ImportHandler) SetSettingsStore

func (h *ImportHandler) SetSettingsStore(store *storage.SettingsStore)

SetSettingsStore sets the settings store for reading defaults.

func (*ImportHandler) SetTunnelsHandler

func (h *ImportHandler) SetTunnelsHandler(th *TunnelsHandler)

SetTunnelsHandler sets the tunnels handler for SSE publishing after import.

type IpsetUsageData

type IpsetUsageData struct {
	MaxElem int            `json:"maxElem" example:"65536"`
	Usage   map[string]int `json:"usage"`
}

IpsetUsageData mirrors hydraroute.IpsetUsage.

type IpsetUsageResponse

type IpsetUsageResponse struct {
	Success bool           `json:"success" example:"true"`
	Data    IpsetUsageData `json:"data"`
}

IpsetUsageResponse is the envelope for GET /hydraroute/ipset-usage.

type KmodLoader

type KmodLoader interface {
	ModuleExists() bool
	IsLoaded() bool
	Model() string
	SoC() kmod.SoC
	OnDiskVersion() string
}

KmodLoader provides kernel module status.

type LANSegmentEntryDTO

type LANSegmentEntryDTO struct {
	Name   string `json:"name" example:"Home"`
	Label  string `json:"label" example:"Home"`
	Subnet string `json:"subnet" example:"192.168.1.0/24"`
}

LANSegmentEntryDTO is a single LAN bridge entry returned by the listing endpoint.

type LANSegmentsListResponse

type LANSegmentsListResponse struct {
	Success bool                 `json:"success" example:"true"`
	Data    []LANSegmentEntryDTO `json:"data"`
}

LANSegmentsListResponse is the envelope for GET /managed-servers/lan-segments.

type LogEntryDTO

type LogEntryDTO struct {
	Timestamp string `json:"timestamp" example:"2024-01-15T10:30:00Z"`
	Level     string `json:"level" example:"info"`
	Group     string `json:"group" example:"singbox"`
	Subgroup  string `json:"subgroup" example:"dns"`
	Action    string `json:"action" example:"run"`
	Target    string `json:"target" example:"dns"`
	Message   string `json:"message" example:"lookup succeed for node.example.org: 203.0.113.77"`
	Sanitized bool   `json:"sanitized" example:"false"`
}

LogEntryDTO mirrors frontend LogEntry.

type LoggingHandler

type LoggingHandler struct {
	// contains filtered or unexported fields
}

LoggingHandler handles logging API endpoints.

func NewLoggingHandler

func NewLoggingHandler(svc *logging.Service, appLogger logging.AppLogger) *LoggingHandler

NewLoggingHandler creates a new logging handler.

func (*LoggingHandler) ClearLogs

func (h *LoggingHandler) ClearLogs(w http.ResponseWriter, r *http.Request)

ClearLogs removes all entries from the requested bucket.

POST /api/logs/clear?bucket=app|singbox

@Summary		Clear logs
@Description	Clears all entries from the requested bucket. `bucket` is required — there is no implicit "clear everything" since app and sing-box logs serve different audiences.
@Tags			logs
@Produce		json
@Security		CookieAuth
@Param			bucket	query		string	true	"Bucket to clear"	Enums(app, singbox)
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/logs/clear [post]

func (*LoggingHandler) GetLogs

func (h *LoggingHandler) GetLogs(w http.ResponseWriter, r *http.Request)

GetLogs returns log entries from the requested bucket with optional filtering and pagination.

GET /api/logs?bucket=app|singbox&group=&subgroup=&level=&limit=&offset=&since=&sanitize=

@Summary		Get logs
@Description	Returns log entries from the selected bucket. `bucket=app` (default) covers tunnel/routing/server/system events; `bucket=singbox` covers sing-box forwarder events isolated from app history. By default target/message are backend-masked; pass `sanitize=false` only when the authenticated admin UI explicitly reveals raw logs.
@Tags			logs
@Produce		json
@Security		CookieAuth
@Param			bucket		query		string	false	"Bucket selector"		Enums(app, singbox)
@Param			group		query		[]string	false	"Filter by group (repeat param or comma-separated values)"	collectionFormat(multi)
@Param			subgroup	query		[]string	false	"Filter by subgroup (repeat param or comma-separated values)"	collectionFormat(multi)
@Param			level		query		string	false	"Filter by level"
@Param			limit		query		int		false	"Max entries to return (default 200)"
@Param			offset		query		int		false	"Skip first N matching entries"
@Param			since		query		int		false	"Unix seconds — return only entries strictly after this"
@Param			sanitize	query		bool	false	"Return backend-sanitized target/message values; default true, set false to reveal raw values"
@Success		200	{object}	LogsResponseEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/logs [get]

func (*LoggingHandler) GetSubgroups

func (h *LoggingHandler) GetSubgroups(w http.ResponseWriter, r *http.Request)

GetSubgroups returns the static catalog of subgroups for the requested group. Used by the frontend to render a second-row chip filter.

GET /api/logs/subgroups?group=routing

@Summary		List known subgroups for a group
@Description	Returns the static subgroup catalog from internal/logging.KnownSubgroups. Order is presentation-stable. Empty group returns 400.
@Tags			logs
@Produce		json
@Security		CookieAuth
@Param			group	query		string	true	"Group name"	Enums(tunnel, routing, server, system, singbox)
@Success		200	{object}	SubgroupsResponseEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Router			/logs/subgroups [get]

func (*LoggingHandler) PublishSnapshot

func (h *LoggingHandler) PublishSnapshot()

PublishSnapshot is a retained no-op hook — the legacy `snapshot:logs` SSE event was removed (state-sync redesign), and the frontend now fetches logs via REST. Keeping the method preserves the callback wiring in server.go / settings.go without forcing a broader refactor.

func (*LoggingHandler) SetEventBus

func (h *LoggingHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus for SSE snapshot publishing.

type LoggingSettingsDTO

type LoggingSettingsDTO struct {
	Enabled           bool   `json:"enabled" example:"true"`
	MaxAge            int    `json:"maxAge" example:"2"`
	LogLevel          string `json:"logLevel" example:"info"`
	SingboxLogLevel   string `json:"singboxLogLevel" example:"trace" enums:"trace,debug,info,warn,error,fatal,panic"`
	AppMaxEntries     int    `json:"appMaxEntries" example:"5000"`
	SingboxMaxEntries int    `json:"singboxMaxEntries" example:"5000"`
}

LoggingSettingsDTO mirrors frontend LoggingSettings.

type LoginRequest

type LoginRequest struct {
	Login    string `json:"login"`
	Password string `json:"password"`
}

LoginRequest is the request body for login.

type LoginResponseRaw

type LoginResponseRaw struct {
	Success bool   `json:"success" example:"true"`
	Login   string `json:"login" example:"admin"`
}

LoginResponseRaw is the raw response for POST /auth/login.

type LogsData

type LogsData struct {
	Enabled         bool          `json:"enabled" example:"true"`
	Logs            []LogEntryDTO `json:"logs"`
	Total           int           `json:"total" example:"42"`
	Bucket          string        `json:"bucket" example:"singbox"`
	BufferSize      int           `json:"bufferSize" example:"123"`
	BufferCapacity  int           `json:"bufferCapacity" example:"5000"`
	Sanitized       bool          `json:"sanitized" example:"false"`
	OldestTimestamp string        `json:"oldestTimestamp,omitempty" example:"2024-01-15T08:00:00Z"`
}

LogsData mirrors frontend LogsResponse.

type LogsResponse

type LogsResponse struct {
	Enabled         bool          `json:"enabled"`
	Logs            []LogEntryDTO `json:"logs"`
	Total           int           `json:"total"`
	Bucket          string        `json:"bucket"`
	BufferSize      int           `json:"bufferSize"`
	BufferCapacity  int           `json:"bufferCapacity"`
	Sanitized       bool          `json:"sanitized"`
	OldestTimestamp string        `json:"oldestTimestamp,omitempty"`
}

LogsResponse represents the response for get logs endpoint.

type LogsResponseEnvelope

type LogsResponseEnvelope struct {
	Success bool     `json:"success" example:"true"`
	Data    LogsData `json:"data"`
}

LogsResponseEnvelope is the envelope for GET /logs.

type ManagedPeerDTO

type ManagedPeerDTO struct {
	PublicKey    string `json:"publicKey" example:"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH="`
	PrivateKey   string `json:"privateKey" example:"IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII="`
	PresharedKey string `json:"presharedKey" example:"JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ="`
	Description  string `json:"description" example:"My Phone"`
	TunnelIP     string `json:"tunnelIP" example:"10.10.0.2"`
	DNS          string `json:"dns,omitempty" example:"8.8.8.8"`
	Enabled      bool   `json:"enabled" example:"true"`
}

ManagedPeerDTO mirrors frontend ManagedPeer.

type ManagedPeerResponse

type ManagedPeerResponse struct {
	Success bool           `json:"success" example:"true"`
	Data    ManagedPeerDTO `json:"data"`
}

ManagedPeerResponse is the envelope for endpoints that return a single managed-server peer (POST /managed-servers/{id}/peers).

type ManagedPeerStatsDTO

type ManagedPeerStatsDTO struct {
	PublicKey     string `json:"publicKey" example:"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF="`
	Endpoint      string `json:"endpoint" example:"5.6.7.8:54321"`
	RxBytes       int64  `json:"rxBytes" example:"2097152"`
	TxBytes       int64  `json:"txBytes" example:"1048576"`
	LastHandshake string `json:"lastHandshake" example:"2024-01-15T10:30:00Z"`
	Online        bool   `json:"online" example:"true"`
}

ManagedPeerStatsDTO mirrors frontend ManagedPeerStats.

type ManagedServerBackupDTO

type ManagedServerBackupDTO struct {
	InterfaceName string           `json:"interfaceName" example:"Wireguard1"`
	Description   string           `json:"description,omitempty" example:"My VPN"`
	Address       string           `json:"address" example:"10.10.0.1"`
	Mask          string           `json:"mask" example:"255.255.255.0"`
	ListenPort    int              `json:"listenPort" example:"51821"`
	Endpoint      string           `json:"endpoint,omitempty" example:"vpn.example.com:51821"`
	DNS           string           `json:"dns,omitempty" example:"8.8.8.8"`
	MTU           int              `json:"mtu,omitempty" example:"1420"`
	NATEnabled    bool             `json:"natEnabled,omitempty" example:"true"`
	PrivateKey    string           `json:"privateKey,omitempty" example:"oA..."`
	Policy        string           `json:"policy" example:"none"`
	Peers         []ManagedPeerDTO `json:"peers"`
	I1            string           `json:"i1,omitempty"`
	I2            string           `json:"i2,omitempty"`
	I3            string           `json:"i3,omitempty"`
	I4            string           `json:"i4,omitempty"`
	I5            string           `json:"i5,omitempty"`
	ASC           json.RawMessage  `json:"asc,omitempty" swaggertype:"object"`
}

ManagedServerBackupDTO mirrors storage.ManagedServer for backup/restore transport. Exists in api/ (rather than reusing managed.ManagedServerExport directly) so swag can resolve it without crossing package boundaries — matches the existing ManagedServerDTO pattern but includes the secret fields (PrivateKey, I1, I2) needed for full restore.

type ManagedServerBackupFile

type ManagedServerBackupFile struct {
	Version        int                      `json:"version"`
	Type           string                   `json:"type"`
	ExportedAt     time.Time                `json:"exportedAt"`
	ManagedServers []ManagedServerBackupDTO `json:"managedServers"`
	Warnings       []BackupWarningDTO       `json:"warnings,omitempty"`
}

ManagedServerBackupFile is the on-disk JSON shape.

type ManagedServerBackupHandler

type ManagedServerBackupHandler struct {
	// contains filtered or unexported fields
}

ManagedServerBackupHandler exposes Export / Import / Drift / RestoreDrift.

func NewManagedServerBackupHandler

func NewManagedServerBackupHandler(svc *managed.Service) *ManagedServerBackupHandler

NewManagedServerBackupHandler creates a new backup handler.

func (*ManagedServerBackupHandler) Drift

Drift handles GET /api/managed/drift.

@Summary		List managed servers missing from NDMS
@Description	Returns settings.json entries whose NDMS interface is absent.
@Tags			managed
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ManagedServerDriftEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/managed/drift [get]

func (*ManagedServerBackupHandler) Export

Export handles GET /api/managed/export.

@Summary		Export all managed servers
@Description	Returns a JSON backup file with every managed server including private keys.
@Tags			managed
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ManagedServerExportEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/managed/export [get]

func (*ManagedServerBackupHandler) Import

Import handles POST /api/managed/import.

@Summary		Import a managed-server backup
@Description	Restores managed servers from a backup file. Per-server atomic with pre-flight conflict detection.
@Tags			managed
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		ManagedServerImportRequest	true	"backup contents + options"
@Success		200		{object}	ManagedServerImportEnvelope
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed/import [post]

func (*ManagedServerBackupHandler) RestoreDrift

RestoreDrift handles POST /api/managed/restore-drift.

@Summary		Restore drifted managed servers
@Description	Detects drift internally then runs Restore on it. Convenience entry for the boot-time banner.
@Tags			managed
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		ManagedServerRestoreDriftRequest	false	"options"
@Success		200		{object}	ManagedServerImportEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed/restore-drift [post]

func (*ManagedServerBackupHandler) SetEventBus

func (h *ManagedServerBackupHandler) SetEventBus(bus *events.Bus)

SetEventBus wires the SSE bus so Import and RestoreDrift can publish resource:invalidated after a successful restore.

type ManagedServerDTO

type ManagedServerDTO struct {
	InterfaceName string           `json:"interfaceName" example:"Wireguard1"`
	Address       string           `json:"address" example:"10.10.0.1"`
	Mask          string           `json:"mask" example:"255.255.255.0"`
	ListenPort    int              `json:"listenPort" example:"51821"`
	Endpoint      string           `json:"endpoint,omitempty" example:"203.0.113.42:51821"`
	DNS           string           `json:"dns,omitempty" example:"8.8.8.8"`
	MTU           int              `json:"mtu,omitempty" example:"1420"`
	NatEnabled    bool             `json:"natEnabled,omitempty" example:"true"`
	NATMode       string           `json:"natMode,omitempty" example:"internet-only"`
	LANSegments   []string         `json:"lanSegments,omitempty" example:"Home"`
	Policy        string           `json:"policy" example:"default"`
	Peers         []ManagedPeerDTO `json:"peers"`
}

ManagedServerDTO mirrors frontend ManagedServer.

type ManagedServerDriftEnvelope

type ManagedServerDriftEnvelope struct {
	Success bool                       `json:"success" example:"true"`
	Data    ManagedServerDriftResponse `json:"data"`
}

ManagedServerDriftEnvelope is the wire shape of GET /api/managed/drift.

type ManagedServerDriftResponse

type ManagedServerDriftResponse struct {
	Drift []ManagedServerBackupDTO `json:"drift"`
}

ManagedServerDriftResponse is the response of GET /api/managed/drift.

type ManagedServerExportEnvelope

type ManagedServerExportEnvelope struct {
	Success bool                    `json:"success" example:"true"`
	Data    ManagedServerBackupFile `json:"data"`
}

ManagedServerExportEnvelope is the wire shape of GET /api/managed/export.

type ManagedServerHandler

type ManagedServerHandler struct {
	// contains filtered or unexported fields
}

ManagedServerHandler handles managed WireGuard server operations.

func NewManagedServerHandler

func NewManagedServerHandler(svc managed.ManagedServerService) *ManagedServerHandler

NewManagedServerHandler creates a new managed server handler.

func (*ManagedServerHandler) ASC

ASC handles GET/PUT for ASC parameters of a managed server.

GET /api/managed-servers/{id}/asc — read params PUT /api/managed-servers/{id}/asc — write params

@Summary		Get/set managed-server ASC params
@Description	GET returns ASCParamsResponse with the current AWG signature/obfuscation params object. PUT writes new params (raw object whose shape depends on the active signature preset) and returns the fresh ServersSnapshot.
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string	true	"Server id"
@Param			body	body		object	false	"ASC params object (PUT only)"
@Success		200		{object}	ASCParamsResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/asc [get]
@Router			/managed-servers/{id}/asc [put]

func (*ManagedServerHandler) AddPeer

AddPeer adds a new peer to a managed server. POST /api/managed-servers/{id}/peers

@Summary		Add managed-server peer
@Description	Adds a new peer to the named managed server. The pubkey is generated server-side if absent.
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string					true	"Server id"
@Param			body	body		AddPeerRequestDTO	true	"Peer payload"
@Success		200		{object}	ManagedPeerResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/peers [post]

func (*ManagedServerHandler) Collection

func (h *ManagedServerHandler) Collection(w http.ResponseWriter, r *http.Request)

Collection dispatches the exact /api/managed-servers endpoint:

  • GET → List
  • POST → Create

func (*ManagedServerHandler) Create

Create creates a new managed WireGuard server. The id is allocated by the service (next free Wireguard{N} slot) — the request body has no id. POST /api/managed-servers

@Summary		Create managed server
@Description	Creates a new managed WireGuard server. The id is allocated server-side (next free Wireguard{N} slot).
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		CreateServerRequestDTO	true	"Address, mask, port, ASC, name"
@Success		200		{object}	ManagedServerResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers [post]

func (*ManagedServerHandler) Delete

Delete removes a managed server and all its peers. DELETE /api/managed-servers/{id}

@Summary		Delete managed server
@Description	Removes the named managed server along with all its peers. Returns the fresh ServersSnapshot.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Param			id	path		string	true	"Server id"
@Success		200	{object}	ServersAllResponse
@Failure		404	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/managed-servers/{id} [delete]

func (*ManagedServerHandler) DeletePeer

func (h *ManagedServerHandler) DeletePeer(w http.ResponseWriter, r *http.Request, id, pubkey string)

DeletePeer removes a peer from a managed server. DELETE /api/managed-servers/{id}/peers/{pubkey}

@Summary		Delete managed-server peer
@Description	Removes the peer identified by pubkey from the named managed server.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Param			id		path		string	true	"Server id"
@Param			pubkey	path		string	true	"Peer public key (URL-encoded)"
@Success		200		{object}	ServersAllResponse
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/peers/{pubkey} [delete]

func (*ManagedServerHandler) Get

Get returns a single managed server by id. GET /api/managed-servers/{id}

@Summary		Get managed server
@Description	Returns a single managed server by id (Wireguard{N}).
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Param			id	path		string	true	"Server id (e.g. Wireguard0)"
@Success		200	{object}	ManagedServerResponse
@Failure		404	{object}	APIErrorEnvelope
@Failure		405	{object}	APIErrorEnvelope
@Router			/managed-servers/{id} [get]

func (*ManagedServerHandler) GetPolicies

func (h *ManagedServerHandler) GetPolicies(w http.ResponseWriter, r *http.Request)

GetPolicies returns every IP Policy profile available on the router. This is a global catalog — not per-server — so it lives at the collection level even though the consumer is a per-server dropdown. GET /api/managed-servers/policies

@Summary		List IP Policy profiles
@Description	Returns the global router catalog of IP Policy profiles for use by the per-server policy dropdown. Always a JSON array, never null.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Success		200	{object}	PoliciesListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/managed-servers/policies [get]

func (*ManagedServerHandler) LANSegments

func (h *ManagedServerHandler) LANSegments(w http.ResponseWriter, r *http.Request, id string)

LANSegments sets the LAN bridge segments accessible to peers of one managed server. POST /api/managed-servers/{id}/lan-segments

@Summary		Set managed-server LAN segments
@Description	Configures which LAN bridge segments (by NDMS interface name) peers of the managed server are allowed to reach via ACL-based forwarding.
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string					true	"Server id"
@Param			body	body		SetLANSegmentsRequest	true	"LAN segment names"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/lan-segments [post]

func (*ManagedServerHandler) List

List returns every managed server. Always emits a JSON array (never null). GET /api/managed-servers

@Summary		List managed servers
@Description	Returns every managed server (id, address, listen port, peers, ASC, NAT, enabled flag, ...). Always a JSON array, never null.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ManagedServersListResponse
@Failure		405	{object}	APIErrorEnvelope
@Router			/managed-servers [get]

func (*ManagedServerHandler) ListLANSegments

func (h *ManagedServerHandler) ListLANSegments(w http.ResponseWriter, r *http.Request)

ListLANSegments returns the router LAN bridge catalog for the UI picker. GET /api/managed-servers/lan-segments

@Summary		List router LAN segments
@Description	Returns the router's LAN bridge catalog (name, label, subnet) for use by the per-server LAN segment picker. Always a JSON array, never null.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Success		200	{object}	LANSegmentsListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/managed-servers/lan-segments [get]

func (*ManagedServerHandler) NAT

NAT enables or disables NAT on one managed server interface. POST /api/managed-servers/{id}/nat

@Summary		Toggle managed-server NAT
@Description	Enables or disables NAT (masquerade) on the named managed server interface.
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string					true	"Server id"
@Param			body	body		SetNATModeRequest	true	"NAT mode"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/nat [post]

func (*ManagedServerHandler) PeerConf

func (h *ManagedServerHandler) PeerConf(w http.ResponseWriter, r *http.Request, id, pubkey string)

PeerConf returns the WireGuard client .conf file for a peer. GET /api/managed-servers/{id}/peers/{pubkey}/conf

@Summary		Generate peer .conf
@Description	Returns the WireGuard client .conf for the peer identified by pubkey on the named managed server.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Param			id		path		string	true	"Server id"
@Param			pubkey	path		string	true	"Peer public key (URL-encoded)"
@Success		200		{object}	PeerConfResponse
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/peers/{pubkey}/conf [get]

func (*ManagedServerHandler) Restart

Restart accepts a restart/start command for one managed server. POST /api/managed-servers/{id}/restart

@Summary		Restart or start managed server
@Description	If the managed server is up, restarts it with down -> pause -> up. If it is down, starts it. The command is accepted quickly and executed in background so a client connected through this server does not cancel the operation.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Param			id	path	string	true	"Server id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		404	{object}	APIErrorEnvelope
@Failure		405	{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/restart [post]

func (*ManagedServerHandler) SetEnabled

func (h *ManagedServerHandler) SetEnabled(w http.ResponseWriter, r *http.Request, id string)

SetEnabled enables or disables one managed server interface. POST /api/managed-servers/{id}/enabled

@Summary		Toggle managed-server enabled
@Description	Brings the named managed server interface up or down (and persists the desired state).
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string					true	"Server id"
@Param			body	body		EnabledToggleRequest	true	"Enabled flag"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/enabled [post]

func (*ManagedServerHandler) SetPolicy

func (h *ManagedServerHandler) SetPolicy(w http.ResponseWriter, r *http.Request, id string)

SetPolicy updates the ip hotspot policy for one managed server interface. POST /api/managed-servers/{id}/policy

@Summary		Set managed-server IP policy
@Description	Updates the IP hotspot policy bound to the managed server interface. The policy must exist (see /managed-servers/policies).
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string					true	"Server id"
@Param			body	body		SetServerPolicyRequest	true	"Policy id (router-side)"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/policy [post]

func (*ManagedServerHandler) SetServersHandler

func (h *ManagedServerHandler) SetServersHandler(s *ServersHandler)

SetServersHandler sets the servers handler for shared SSE publishing.

func (*ManagedServerHandler) Stats

Stats returns runtime statistics for one managed server's peers. GET /api/managed-servers/{id}/stats

@Summary		Get managed-server peer stats
@Description	Returns per-peer runtime stats (handshake, rx/tx) for the named managed server.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Param			id	path		string	true	"Server id"
@Success		200	{object}	ManagedServerStatsResponse
@Failure		404	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/stats [get]

func (*ManagedServerHandler) Subtree

Subtree dispatches every URL under /api/managed-servers/, splitting on '/' to extract the id and any further sub-path. See the documented route table in this package's package doc / Task 5 plan for the full mapping.

func (*ManagedServerHandler) SuggestAddress

func (h *ManagedServerHandler) SuggestAddress(w http.ResponseWriter, r *http.Request)

SuggestAddress returns a free private /24 for the create-server UI. GET /api/managed-servers/suggest-address

@Summary		Suggest managed-server address
@Description	Returns a free private /24 (address, mask) for the create-server UI. Avoids collisions with already-used managed-server subnets.
@Tags			managed-servers
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SuggestAddressResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/managed-servers/suggest-address [get]

func (*ManagedServerHandler) TogglePeer

func (h *ManagedServerHandler) TogglePeer(w http.ResponseWriter, r *http.Request, id, pubkey string)

TogglePeer enables or disables a peer. POST /api/managed-servers/{id}/peers/{pubkey}/toggle

@Summary		Toggle managed-server peer enabled
@Description	Enables or disables the peer identified by pubkey on the named managed server.
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string					true	"Server id"
@Param			pubkey	path		string					true	"Peer public key (URL-encoded)"
@Param			body	body		EnabledToggleRequest	true	"Enabled flag"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/peers/{pubkey}/toggle [post]

func (*ManagedServerHandler) Update

Update updates a managed server's address and/or listen port. PUT /api/managed-servers/{id}

@Summary		Update managed server
@Description	Updates a managed server's address, listen port, name, and/or other top-level fields. Returns the fresh ServersSnapshot.
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string						true	"Server id"
@Param			body	body		UpdateServerRequestDTO	true	"Update payload"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id} [put]

func (*ManagedServerHandler) UpdatePeer

func (h *ManagedServerHandler) UpdatePeer(w http.ResponseWriter, r *http.Request, id, pubkey string)

UpdatePeer updates an existing peer of a managed server. PUT /api/managed-servers/{id}/peers/{pubkey}

@Summary		Update managed-server peer
@Description	Updates fields (name, allowed-ips, ...) of the peer identified by pubkey on the named managed server.
@Tags			managed-servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		path		string						true	"Server id"
@Param			pubkey	path		string						true	"Peer public key (URL-encoded)"
@Param			body	body		UpdatePeerRequestDTO	true	"Peer update payload"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/managed-servers/{id}/peers/{pubkey} [put]

type ManagedServerImportEnvelope

type ManagedServerImportEnvelope struct {
	Success bool                         `json:"success" example:"true"`
	Data    ManagedServerRestoreResponse `json:"data"`
}

ManagedServerImportEnvelope is the wire shape of POST /api/managed/import and POST /api/managed/restore-drift.

type ManagedServerImportRequest

type ManagedServerImportRequest struct {
	ManagedServers []ManagedServerBackupDTO `json:"managedServers"`
	Options        RestoreOptionsDTO        `json:"options"`
	Version        int                      `json:"version"`
	Type           string                   `json:"type"`
}

ManagedServerImportRequest is the body of POST /api/managed/import.

type ManagedServerResponse

type ManagedServerResponse struct {
	Success bool             `json:"success" example:"true"`
	Data    ManagedServerDTO `json:"data"`
}

ManagedServerResponse is the envelope for GET /managed-server.

type ManagedServerRestoreDriftRequest

type ManagedServerRestoreDriftRequest struct {
	Options RestoreOptionsDTO `json:"options"`
}

ManagedServerRestoreDriftRequest is the body of POST /api/managed/restore-drift.

type ManagedServerRestoreResponse

type ManagedServerRestoreResponse struct {
	Outcomes []RestoreOutcomeDTO `json:"outcomes"`
}

ManagedServerRestoreResponse is the response of /import and /restore-drift.

type ManagedServerStatsDTO

type ManagedServerStatsDTO struct {
	Status string                `json:"status" example:"up"`
	Peers  []ManagedPeerStatsDTO `json:"peers"`
}

ManagedServerStatsDTO mirrors frontend ManagedServerStats.

type ManagedServerStatsResponse

type ManagedServerStatsResponse struct {
	Success bool                  `json:"success" example:"true"`
	Data    ManagedServerStatsDTO `json:"data"`
}

ManagedServerStatsResponse is the envelope for GET /managed-servers/{id}/stats. Reuses ManagedServerStatsDTO from servers.go (also referenced by ServersAllData).

type ManagedServersListResponse

type ManagedServersListResponse struct {
	Success bool               `json:"success" example:"true"`
	Data    []ManagedServerDTO `json:"data"`
}

ManagedServersListResponse is the envelope for GET /managed-servers.

type MonitoringCellDTO

type MonitoringCellDTO struct {
	TargetID         string `json:"targetId" example:"target_google"`
	TunnelID         string `json:"tunnelId" example:"tun_abc123"`
	LatencyMs        *int   `json:"latencyMs" swaggertype:"integer" example:"42"`
	OK               bool   `json:"ok" example:"true"`
	ActiveForRestart bool   `json:"activeForRestart" example:"false"`
	IsSelf           bool   `json:"isSelf" example:"false"`
	Ts               string `json:"ts" example:"2024-01-15T10:30:00Z"`
}

MonitoringCellDTO mirrors frontend MonitoringCell.

type MonitoringHandler

type MonitoringHandler struct {
	// contains filtered or unexported fields
}

MonitoringHandler exposes the monitoring matrix endpoints.

func NewMonitoringHandler

func NewMonitoringHandler(svc *monitoring.Service) *MonitoringHandler

NewMonitoringHandler builds a handler with the given service. svc may be nil during partial bootstrap — handlers respond 503 until Start.

func (*MonitoringHandler) GetMatrix

func (h *MonitoringHandler) GetMatrix(w http.ResponseWriter, r *http.Request)

GetMatrix returns the current matrix snapshot. GET /api/monitoring/matrix

@Summary		Get monitoring matrix snapshot
@Description	Returns the latest cross-tunnel × cross-target latency/loss matrix snapshot. Responds 503 until the monitoring service has finished bootstrap. Pass `?force=1` to invalidate the Clash cache and run a fresh probing tick before returning.
@Tags			monitoring
@Produce		json
@Security		CookieAuth
@Param			force	query		int		false	"Set to 1 to force-refresh ICMP/Clash data before snapshot read"
@Success		200		{object}	MonitoringSnapshotResponse
@Failure		405		{object}	APIErrorEnvelope
@Failure		503		{object}	APIErrorEnvelope
@Router			/monitoring/matrix [get]

type MonitoringRefreshService

type MonitoringRefreshService interface {
	RefreshNow(ctx context.Context)
}

MonitoringRefreshService defines the interface for forcing an immediate monitoring snapshot recalculation after settings mutations.

type MonitoringSnapshotData

type MonitoringSnapshotData struct {
	Targets   []MonitoringTargetDTO `json:"targets"`
	Tunnels   []MonitoringTunnelDTO `json:"tunnels"`
	Cells     []MonitoringCellDTO   `json:"cells"`
	UpdatedAt string                `json:"updatedAt" example:"2024-01-15T10:30:00Z"`
}

MonitoringSnapshotData mirrors frontend MonitoringSnapshot.

type MonitoringSnapshotResponse

type MonitoringSnapshotResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    MonitoringSnapshotData `json:"data"`
}

MonitoringSnapshotResponse is the envelope for GET /monitoring/matrix.

type MonitoringTargetDTO

type MonitoringTargetDTO struct {
	ID   string `json:"id" example:"target_google"`
	Host string `json:"host" example:"https://www.google.com"`
	Name string `json:"name" example:"Google"`
}

MonitoringTargetDTO mirrors frontend MonitoringTarget.

type MonitoringTunnelDTO

type MonitoringTunnelDTO struct {
	ID              string `json:"id" example:"tun_abc123"`
	Name            string `json:"name" example:"My VPN"`
	IfaceName       string `json:"ifaceName" example:"nwg0"`
	PingcheckTarget string `json:"pingcheckTarget" example:"target_google"`
	SelfTarget      string `json:"selfTarget" example:"target_self_tun_abc123"`
	SelfMethod      string `json:"selfMethod" example:"http"`
}

MonitoringTunnelDTO mirrors frontend MonitoringTunnel.

type MoveRejectedToInfoRequest

type MoveRejectedToInfoRequest struct {
	MemberTag string `json:"memberTag"`
}

MoveRejectedToInfoRequest is the body for POST .../rejected/to-info.

type NDMSHandler

type NDMSHandler struct {
	// contains filtered or unexported fields
}

NDMSHandler exposes read-only NDMS status endpoints that are not scoped to a more specific handler (routing, tunnels, managed, etc.).

func NewNDMSHandler

func NewNDMSHandler(save *ndmscommand.SaveCoordinator) *NDMSHandler

NewNDMSHandler constructs a handler backed by the live SaveCoordinator. The coordinator pointer must be non-nil; main.go builds it before the HTTP server starts.

func (*NDMSHandler) GetSaveStatus

func (h *NDMSHandler) GetSaveStatus(w http.ResponseWriter, r *http.Request)

GetSaveStatus returns the current NDMS save-coordinator status as JSON. GET /api/ndms/save-status

@Summary		NDMS save coordinator status
@Tags			ndms
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SaveStatusDTO
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/ndms/save-status [get]

type NativePingCheckStatusDTO

type NativePingCheckStatusDTO struct {
	Exists       bool   `json:"exists" example:"true"`
	Host         string `json:"host" example:"https://www.google.com"`
	Mode         string `json:"mode" example:"connect"`
	Interval     int    `json:"interval" example:"30"`
	MaxFails     int    `json:"maxFails" example:"3"`
	MinSuccess   int    `json:"minSuccess" example:"1"`
	Timeout      int    `json:"timeout" example:"5"`
	Restart      bool   `json:"restart" example:"true"`
	Bound        bool   `json:"bound" example:"true"`
	Status       string `json:"status" example:"alive"`
	FailCount    int    `json:"failCount" example:"0"`
	SuccessCount int    `json:"successCount" example:"120"`
}

NativePingCheckStatusDTO mirrors frontend NativePingCheckStatus.

type NativePingCheckStatusResponse

type NativePingCheckStatusResponse struct {
	Success bool                     `json:"success" example:"true"`
	Data    NativePingCheckStatusDTO `json:"data"`
}

NativePingCheckStatusResponse is the envelope for GET /tunnels/pingcheck.

type OkData

type OkData struct {
	Ok bool `json:"ok" example:"true"`
}

OkData is the {ok: true} confirmation payload returned by mutation endpoints that have no entity to send back.

type OkResponse

type OkResponse struct {
	Success bool   `json:"success" example:"true"`
	Data    OkData `json:"data"`
}

OkResponse is the typed envelope for endpoints that return only a confirmation (success + ok). Use as @Success {object} OkResponse.

type OversizedTagDTO

type OversizedTagDTO struct {
	Name  string `json:"name" example:"netflix"`
	Count int    `json:"count" example:"82000"`
	File  string `json:"file" example:"/opt/etc/hrneo/geosite.db"`
}

OversizedTagDTO mirrors hydraroute.OversizedTag.

type OversizedTagsData

type OversizedTagsData struct {
	Installed bool              `json:"installed" example:"true"`
	MaxElem   int               `json:"maxelem" example:"65536"`
	Tags      []OversizedTagDTO `json:"tags"`
}

OversizedTagsData reports geoip tags excluded by HrNeo together with the active IpsetMaxElem so the frontend can render the disabled-tags pane and enforce picker limits.

type OversizedTagsResponse

type OversizedTagsResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    OversizedTagsData `json:"data"`
}

OversizedTagsResponse is the envelope for GET /hydraroute/oversized-tags.

type PeerConfData

type PeerConfData struct {
	Conf string `json:"conf" example:"[Interface]\nPrivateKey = ..."`
}

PeerConfData carries a generated WireGuard client .conf as a string.

type PeerConfResponse

type PeerConfResponse struct {
	Success bool         `json:"success" example:"true"`
	Data    PeerConfData `json:"data"`
}

PeerConfResponse is the envelope for GET /managed-servers/{id}/peers/{pubkey}/conf.

type PermitInterfaceRequest

type PermitInterfaceRequest struct {
	Name      string `json:"name" example:"Policy0"`
	Interface string `json:"interface" example:"Wireguard0"`
	Order     int    `json:"order" example:"0"`
}

PermitInterfaceRequest is the body for POST /access-policies/permit.

type PingCheckDefaultsDTO

type PingCheckDefaultsDTO struct {
	Method        string `json:"method" example:"http"`
	Target        string `json:"target" example:"8.8.8.8"`
	Interval      int    `json:"interval" example:"30"`
	DeadInterval  int    `json:"deadInterval" example:"120"`
	FailThreshold int    `json:"failThreshold" example:"3"`
}

PingCheckDefaultsDTO mirrors frontend PingCheckDefaults.

type PingCheckHandler

type PingCheckHandler struct {
	// contains filtered or unexported fields
}

PingCheckHandler handles ping check API endpoints.

func NewPingCheckHandler

func NewPingCheckHandler(service PingCheckService, tunnels *storage.AWGTunnelStore, nwgOp *nwg.OperatorNativeWG, appLogger logging.AppLogger) *PingCheckHandler

NewPingCheckHandler creates a new ping check handler.

func (*PingCheckHandler) CheckNow

func (h *PingCheckHandler) CheckNow(w http.ResponseWriter, r *http.Request)

CheckNow triggers an immediate check on all tunnels.

@Summary		Trigger ping check now
@Tags			pingcheck
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/pingcheck/check-now [post]

func (*PingCheckHandler) ClearLogs

func (h *PingCheckHandler) ClearLogs(w http.ResponseWriter, r *http.Request)

ClearLogs removes all ping check log entries.

@Summary		Clear ping check logs
@Tags			pingcheck
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/pingcheck/logs/clear [post]

func (*PingCheckHandler) ConfigureTunnelPingCheck

func (h *PingCheckHandler) ConfigureTunnelPingCheck(w http.ResponseWriter, r *http.Request)

ConfigureTunnelPingCheck creates/updates NDMS ping-check for a nativewg tunnel. POST /api/tunnels/pingcheck?id=xxx

@Summary		Configure per-tunnel NDMS ping-check
@Tags			pingcheck
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/pingcheck [post]

func (*PingCheckHandler) GetLogs

func (h *PingCheckHandler) GetLogs(w http.ResponseWriter, r *http.Request)

GetLogs returns ping check logs.

@Summary		Ping check logs
@Tags			pingcheck
@Produce		json
@Security		CookieAuth
@Param			tunnelId	query	string	false	"Filter by tunnel id"
@Success		200	{object}	PingLogsResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/pingcheck/logs [get]

func (*PingCheckHandler) GetStatus

func (h *PingCheckHandler) GetStatus(w http.ResponseWriter, r *http.Request)

GetStatus returns the current status of all monitored tunnels.

@Summary		Ping check status
@Tags			pingcheck
@Produce		json
@Security		CookieAuth
@Success		200	{object}	PingCheckStatusResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/pingcheck/status [get]

func (*PingCheckHandler) GetTunnelPingCheckStatus

func (h *PingCheckHandler) GetTunnelPingCheckStatus(w http.ResponseWriter, r *http.Request)

GetTunnelPingCheckStatus returns NDMS ping-check status for a single nativewg tunnel. GET /api/tunnels/pingcheck?id=xxx

@Summary		Get per-tunnel NDMS ping-check
@Tags			pingcheck
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	NativePingCheckStatusResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/pingcheck [get]

func (*PingCheckHandler) PublishSnapshot

func (h *PingCheckHandler) PublishSnapshot()

PublishSnapshot publishes a resource:invalidated hint for pingcheck so subscribed polling stores refetch. The legacy `snapshot:pingcheck` event was removed (Task 12) — the frontend status list is now a polling store. Logs are still pushed via `pingcheck:log` stream, untouched.

func (*PingCheckHandler) RemoveTunnelPingCheck

func (h *PingCheckHandler) RemoveTunnelPingCheck(w http.ResponseWriter, r *http.Request)

RemoveTunnelPingCheck removes NDMS ping-check for a nativewg tunnel. POST /api/tunnels/pingcheck/remove?id=xxx

@Summary		Remove per-tunnel NDMS ping-check
@Tags			pingcheck
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/pingcheck/remove [post]

func (*PingCheckHandler) SetEventBus

func (h *PingCheckHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus for SSE invalidation hints.

func (*PingCheckHandler) SetOrchestrator

func (h *PingCheckHandler) SetOrchestrator(orch *orchestrator.Orchestrator)

SetOrchestrator wires the orchestrator for in-memory state sync after ping-check enable/disable mutations. Without this the decide layer keeps a stale PingCheck.Enabled flag and fires spurious ActionRemovePingCheck on the next lifecycle event.

type PingCheckService

type PingCheckService interface {
	GetStatus() []pingcheck.TunnelStatus
	GetLogs() []pingcheck.LogEntry
	GetTunnelLogs(tunnelID string) []pingcheck.LogEntry
	ClearLogs()
	CheckAllNow()
	IsEnabled() bool
	StartMonitoringAllRunning()
	StopMonitoringAll()
	Stop()
	// Per-tunnel monitoring control
	StartMonitoring(tunnelID, tunnelName string, skipConfigure ...bool)
	StopMonitoring(tunnelID string)
	GetTunnelPingStatus(tunnelID string) pingcheck.TunnelPingInfo
}

PingCheckService defines the interface for ping check operations. Uses pingcheck types directly — no adapter needed.

type PingCheckSettingsDTO

type PingCheckSettingsDTO struct {
	Enabled  bool                 `json:"enabled" example:"true"`
	Defaults PingCheckDefaultsDTO `json:"defaults"`
}

PingCheckSettingsDTO mirrors frontend PingCheckSettings.

type PingCheckStatusData

type PingCheckStatusData struct {
	Enabled bool                  `json:"enabled" example:"true"`
	Tunnels []TunnelPingStatusDTO `json:"tunnels"`
}

PingCheckStatusData mirrors frontend PingCheckStatus.

type PingCheckStatusResponse

type PingCheckStatusResponse struct {
	Success bool                `json:"success" example:"true"`
	Data    PingCheckStatusData `json:"data"`
}

PingCheckStatusResponse is the envelope for GET /pingcheck/status.

type PingCheckToggleService

type PingCheckToggleService interface {
	StartMonitoringAllRunning()
	StopMonitoringAll()
}

PingCheckToggleService defines the interface for ping check toggle operations.

type PingLogEntryDTO

type PingLogEntryDTO struct {
	Timestamp   string `json:"timestamp" example:"2024-01-15T10:30:00Z"`
	TunnelId    string `json:"tunnelId" example:"tun_abc123"`
	TunnelName  string `json:"tunnelName" example:"My VPN"`
	Success     bool   `json:"success" example:"true"`
	Latency     int    `json:"latency" example:"35"`
	Error       string `json:"error" example:""`
	FailCount   int    `json:"failCount" example:"0"`
	Threshold   int    `json:"threshold" example:"3"`
	StateChange string `json:"stateChange" example:""`
}

PingLogEntryDTO mirrors frontend PingLogEntry.

type PingLogsResponse

type PingLogsResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    []PingLogEntryDTO `json:"data"`
}

PingLogsResponse is the envelope for GET /pingcheck/logs.

type PoliciesListResponse

type PoliciesListResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    []PolicyOptionDTO `json:"data"`
}

PoliciesListResponse is the envelope for GET /managed-servers/policies.

type PolicyDeviceDTO

type PolicyDeviceDTO struct {
	MAC      string `json:"mac" example:"aa:bb:cc:dd:ee:ff"`
	IP       string `json:"ip" example:"192.168.1.100"`
	Name     string `json:"name" example:"My Phone"`
	Hostname string `json:"hostname" example:"my-phone"`
	Active   bool   `json:"active" example:"true"`
	Link     string `json:"link" example:"WiFi"`
	Policy   string `json:"policy" example:"default"`
}

PolicyDeviceDTO mirrors frontend PolicyDevice.

type PolicyDevicesListResponse

type PolicyDevicesListResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    []PolicyDeviceDTO `json:"data"`
}

PolicyDevicesListResponse is the envelope for GET /access-policies/devices.

type PolicyGlobalInterfaceDTO

type PolicyGlobalInterfaceDTO struct {
	Name  string `json:"name" example:"nwg0"`
	Label string `json:"label" example:"My VPN"`
	Up    bool   `json:"up" example:"true"`
}

PolicyGlobalInterfaceDTO mirrors frontend PolicyGlobalInterface.

type PolicyInterfacesListResponse

type PolicyInterfacesListResponse struct {
	Success bool                       `json:"success" example:"true"`
	Data    []PolicyGlobalInterfaceDTO `json:"data"`
}

PolicyInterfacesListResponse is the envelope for GET /access-policies/interfaces.

type PolicyOptionDTO

type PolicyOptionDTO struct {
	ID          string `json:"id" example:"Policy0"`
	Description string `json:"description" example:"Default policy"`
}

PolicyOptionDTO mirrors managed.PolicyOption (router IP Policy profile entry).

type PolicyOrderData

type PolicyOrderData struct {
	Order []string `json:"order" example:"default"`
}

PolicyOrderData reports the current HrNeo policy order.

type PolicyOrderResponse

type PolicyOrderResponse struct {
	Success bool            `json:"success" example:"true"`
	Data    PolicyOrderData `json:"data"`
}

PolicyOrderResponse is the envelope for POST /hydraroute/policy-order.

type PresetsHandler

type PresetsHandler struct {
	// contains filtered or unexported fields
}

PresetsHandler serves the unified preset catalog (read-only in U0).

func NewPresetsHandler

func NewPresetsHandler(catalog *presets.Catalog) *PresetsHandler

func (*PresetsHandler) List

List returns the merged preset catalog.

@Summary	List unified presets
@Tags		presets
@Produce	json
@Success	200	{object}	PresetsListResponse
@Router		/presets [get]

type PresetsListResponse

type PresetsListResponse struct {
	Presets []presets.Preset `json:"presets"`
}

PresetsListResponse is the envelope payload for GET /presets.

type PreviewURLRequest

type PreviewURLRequest struct {
	URL     string               `json:"url" example:"https://example.com/subscriptions/demo.txt"`
	Headers []SubscriptionHeader `json:"headers"`
}

PreviewURLRequest is the body for POST /api/singbox/subscriptions/preview. Read-only fetch + parse of a subscription URL without creating anything.

type ProxyConfigResponse

type ProxyConfigResponse struct {
	Success bool                  `json:"success" example:"true"`
	Data    DeviceProxyConfigData `json:"data"`
}

ProxyConfigResponse is the envelope for GET /proxy/config.

type ProxyInstanceResponse

type ProxyInstanceResponse struct {
	Success bool                    `json:"success" example:"true"`
	Data    DeviceProxyInstanceData `json:"data"`
}

ProxyInstanceResponse is the envelope for GET/PUT /proxy/instance.

type ProxyInstancesResponse

type ProxyInstancesResponse struct {
	Success bool                      `json:"success" example:"true"`
	Data    []DeviceProxyInstanceData `json:"data"`
}

ProxyInstancesResponse is the envelope for GET /proxy/instances.

type ProxyListenChoicesData

type ProxyListenChoicesData struct {
	LanIP          string `json:"lanIP" example:"192.168.1.1"`
	SingboxRunning bool   `json:"singboxRunning" example:"true"`
}

ProxyListenChoicesData mirrors the listen-choices payload.

type ProxyListenChoicesResponse

type ProxyListenChoicesResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    ProxyListenChoicesData `json:"data"`
}

ProxyListenChoicesResponse is the envelope for GET /proxy/listen-choices.

type ProxyOutboundsResponse

type ProxyOutboundsResponse struct {
	Success bool                     `json:"success" example:"true"`
	Data    []DeviceProxyOutboundDTO `json:"data"`
}

ProxyOutboundsResponse is the envelope for GET /proxy/outbounds.

type ProxyRuntimeResponse

type ProxyRuntimeResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    DeviceProxyRuntimeData `json:"data"`
}

ProxyRuntimeResponse is the envelope for GET /proxy/runtime.

type RemoveInfoItemRequest

type RemoveInfoItemRequest struct {
	ItemID string `json:"itemId"`
}

RemoveInfoItemRequest is the body for POST .../info/remove.

type RemoveMemberRequest

type RemoveMemberRequest struct {
	MemberTag string `json:"memberTag" example:"sub-demo-003"`
}

RemoveMemberRequest is the body for POST /api/singbox/subscriptions/members/remove. Removing the last member tears the whole subscription down (no meaningful empty subscription); the response carries deleted=true in that case so the UI can navigate away.

type RemoveMemberResponseData

type RemoveMemberResponseData struct {
	Deleted      bool             `json:"deleted" example:"false"`
	Subscription *SubscriptionDTO `json:"subscription,omitempty"`
}

RemoveMemberResponseData is the data envelope for remove-member.

type ResolveHandler

type ResolveHandler struct{}

func NewResolveHandler

func NewResolveHandler() *ResolveHandler

func (*ResolveHandler) Resolve

func (h *ResolveHandler) Resolve(w http.ResponseWriter, r *http.Request)

Resolve performs a DNS lookup (IPv4 only) for routing search.

@Summary		Resolve domain
@Tags			routing
@Produce		json
@Security		CookieAuth
@Param			domain	query	string	true	"Hostname to resolve"
@Success		200	{object}	ResolveResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/routing/resolve [get]

type ResolveResponse

type ResolveResponse struct {
	Domain string   `json:"domain"`
	IPs    []string `json:"ips"`
	Error  string   `json:"error,omitempty"`
}

ResolveResponse is the JSON body for GET /api/routing/resolve.

type RestoreMembersRequest

type RestoreMembersRequest struct {
	MemberTags []string `json:"memberTags" example:"sub-demo-002"`
}

RestoreMembersRequest is the body for POST /api/singbox/subscriptions/members/restore.

type RestoreOptionsDTO

type RestoreOptionsDTO struct {
	AllowRenumber bool `json:"allowRenumber" example:"false"`
}

RestoreOptionsDTO mirrors managed.RestoreOptions on the wire. Defined in api/ so swag resolves it without crossing package boundaries.

type RestoreOutcomeDTO

type RestoreOutcomeDTO struct {
	Name       string   `json:"name" example:"Wireguard1"`
	NewName    string   `json:"newName,omitempty" example:"Wireguard2"`
	Action     string   `json:"action" example:"created"`
	AddedPeers int      `json:"addedPeers,omitempty" example:"2"`
	Conflicts  []string `json:"conflicts,omitempty"`
	Error      string   `json:"error,omitempty"`
}

RestoreOutcomeDTO mirrors managed.RestoreOutcome on the wire.

type RouterDetails

type RouterDetails = routerinfo.RouterDetails

RouterDetails contains extended router metadata derived from NDMS/RCI and local procfs.

type RouterInterfaceDTO

type RouterInterfaceDTO struct {
	Name  string `json:"name" example:"br0"`
	Label string `json:"label" example:"Home Network"`
	Up    bool   `json:"up" example:"true"`
}

RouterInterfaceDTO mirrors frontend RouterInterface.

type RouterStagingStatusResponse

type RouterStagingStatusResponse struct {
	HasDraft   bool                 `json:"hasDraft"`
	DraftedAt  *time.Time           `json:"draftedAt,omitempty"`
	Validation *RouterValidationDTO `json:"validation,omitempty"`
}

RouterStagingStatusResponse is the body of GET /api/singbox/router/staging. HasDraft=false means DraftedAt and Validation are absent.

type RouterStagingValidationError

type RouterStagingValidationError struct {
	Validation *RouterValidationDTO `json:"validation,omitempty"`
	SbCheck    string               `json:"sbCheck,omitempty"`
}

RouterStagingValidationError is the body of 422 responses from /staging/apply. Either Validation or SbCheck is populated (exclusive).

type RouterValidationDTO

type RouterValidationDTO struct {
	Errors []RouterValidationErrorDTO `json:"errors"`
}

RouterValidationDTO carries a structured list of cross-slot validation errors. Used by the staging-status payload (preview) and by 422 responses from /staging/apply.

type RouterValidationErrorDTO

type RouterValidationErrorDTO struct {
	Slot    string `json:"slot"`
	Kind    string `json:"kind"`
	Tag     string `json:"tag,omitempty"`
	InRule  string `json:"inRule,omitempty"`
	Message string `json:"message"`
}

RouterValidationErrorDTO mirrors orchestrator.ValidationError.

type RoutingHandler

type RoutingHandler struct {
	// contains filtered or unexported fields
}

RoutingHandler handles routing API endpoints.

func NewRoutingHandler

func NewRoutingHandler(catalog routing.Catalog, queries *ndmsquery.Queries) *RoutingHandler

NewRoutingHandler creates a new routing handler.

func (*RoutingHandler) Refresh

func (h *RoutingHandler) Refresh(w http.ResponseWriter, r *http.Request)

Refresh drops every NDMS list cache that feeds the routing sections, then posts resource:invalidated hints so every routing polling store refetches on the next tick (or immediately if subscribed). Returns the Missing list from a freshly-built snapshot so the caller can tell whether the retry succeeded. POST /api/routing/refresh

@Summary		Invalidate routing caches
@Tags			routing
@Produce		json
@Security		CookieAuth
@Success		200	{object}	RoutingRefreshResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/routing/refresh [post]

func (*RoutingHandler) SetEventBus

func (h *RoutingHandler) SetEventBus(bus *events.Bus)

SetEventBus wires the SSE bus so refresh can rebroadcast a fresh snapshot to every connected client after invalidating NDMS caches.

func (*RoutingHandler) Tunnels

func (h *RoutingHandler) Tunnels(w http.ResponseWriter, r *http.Request)

Tunnels returns available tunnels for routing dropdowns. GET /api/routing/tunnels

@Summary		Routing tunnel catalog
@Tags			routing
@Produce		json
@Security		CookieAuth
@Success		200	{array}	object
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/routing/tunnels [get]

type RoutingRefreshData

type RoutingRefreshData struct {
	Missing []string `json:"missing" example:""`
}

RoutingRefreshData is the payload for POST /routing/refresh.

type RoutingRefreshResponse

type RoutingRefreshResponse struct {
	Success bool               `json:"success" example:"true"`
	Data    RoutingRefreshData `json:"data"`
}

RoutingRefreshResponse is the envelope for POST /routing/refresh.

type RoutingTunnelDTO

type RoutingTunnelDTO struct {
	ID        string `json:"id" example:"tun_abc123"`
	Name      string `json:"name" example:"My VPN"`
	Iface     string `json:"iface,omitempty" example:"nwg0"`
	Type      string `json:"type" example:"managed"`
	Status    string `json:"status" example:"connected"`
	Available bool   `json:"available" example:"true"`
}

RoutingTunnelDTO mirrors frontend RoutingTunnel.

type RoutingTunnelsResponse

type RoutingTunnelsResponse struct {
	Success bool               `json:"success" example:"true"`
	Data    []RoutingTunnelDTO `json:"data"`
}

RoutingTunnelsResponse is the envelope for GET /routing/tunnels.

type SaveStatusDTO

type SaveStatusDTO struct {
	State        string    `json:"state"`
	LastError    string    `json:"lastError,omitempty"`
	LastSaveAt   time.Time `json:"lastSaveAt,omitempty"`
	PendingCount int       `json:"pendingCount"`
}

SaveStatusDTO is the wire shape returned by GET /api/ndms/save-status. State is one of: "idle" | "pending" | "saving" | "error" | "failed". Mirrors the shape UI consumers previously read from the SSE event bus so the polling store keeps the same keys.

type ServerAddPeerRequestDTO

type ServerAddPeerRequestDTO struct {
	Description string `json:"description" example:"My Phone"`
	TunnelIP    string `json:"tunnelIP" example:"10.0.14.2/32"`
}

ServerAddPeerRequestDTO is the body for POST /servers/{name}/peers.

type ServerConfigResponse

type ServerConfigResponse struct {
	Success bool                     `json:"success" example:"true"`
	Data    WireguardServerConfigDTO `json:"data"`
}

ServerConfigResponse is the envelope for GET /servers/config.

type ServerSettingsDTO

type ServerSettingsDTO struct {
	Port      int    `json:"port" example:"8080"`
	Interface string `json:"interface" example:""`
}

ServerSettingsDTO mirrors frontend ServerSettings.

type ServerUpdatePeerRequestDTO

type ServerUpdatePeerRequestDTO struct {
	Description string `json:"description" example:"My Phone"`
	TunnelIP    string `json:"tunnelIP" example:"10.0.14.2/32"`
}

ServerUpdatePeerRequestDTO is the body for PUT /servers/{name}/peers/{pubkey}.

type ServersAllData

type ServersAllData struct {
	Servers      []WireguardServerDTO   `json:"servers"`
	ManagedStats *ManagedServerStatsDTO `json:"managedStats"`
	WANIP        string                 `json:"wanIP" example:"203.0.113.42"`
}

ServersAllData is the composite payload for GET /servers/all.

type ServersAllResponse

type ServersAllResponse struct {
	Success bool           `json:"success" example:"true"`
	Data    ServersAllData `json:"data"`
}

ServersAllResponse is the envelope for GET /servers/all.

type ServersHandler

type ServersHandler struct {
	// contains filtered or unexported fields
}

ServersHandler handles VPN server interface operations. Frontend now polls GET /api/servers/all; this handler only emits resource:invalidated hints on mark/unmark and poller metrics ticks so subscribers refetch immediately instead of waiting for the next poll.

func NewServersHandler

func NewServersHandler(queries *query.Queries, settings *storage.SettingsStore, awgStore *storage.AWGTunnelStore, appLogger logging.AppLogger) *ServersHandler

NewServersHandler creates a new servers handler.

func (*ServersHandler) AddServerPeer

func (h *ServersHandler) AddServerPeer(w http.ResponseWriter, r *http.Request, name string)

AddServerPeer adds a peer to a built-in/marked WireGuard server. POST /api/servers/{name}/peers

@Summary		Add server peer
@Description	Generates a keypair and registers a new peer on the named WireGuard server. Returns the fresh servers snapshot.
@Tags			servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			name	path		string						true	"Interface name (e.g. Wireguard0)"
@Param			body	body		ServerAddPeerRequestDTO	true	"Peer description and tunnel IP"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/{name}/peers [post]

func (*ServersHandler) Config

func (h *ServersHandler) Config(w http.ResponseWriter, r *http.Request)

Config returns RC configuration for .conf generation. GET /api/servers/config?name=Wireguard0

func (*ServersHandler) DeleteServerPeer

func (h *ServersHandler) DeleteServerPeer(w http.ResponseWriter, r *http.Request, name, pubkey string)

DeleteServerPeer removes a peer from a WireGuard server. DELETE /api/servers/{name}/peers/{pubkey}

@Summary		Delete server peer
@Description	Removes the peer with the given public key from the named WireGuard server. Returns the fresh servers snapshot.
@Tags			servers
@Produce		json
@Security		CookieAuth
@Param			name	path		string	true	"Interface name (e.g. Wireguard0)"
@Param			pubkey	path		string	true	"Peer public key"
@Success		200		{object}	ServersAllResponse
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/{name}/peers/{pubkey} [delete]

func (*ServersHandler) Get

Get returns a single server with all peers. GET /api/servers/get?name=Wireguard0

func (*ServersHandler) GetAll

func (h *ServersHandler) GetAll(w http.ResponseWriter, r *http.Request)

GetAll returns the composite servers snapshot (list + managed + stats + wanIP). Replaces the snapshot:servers SSE event — the frontend polls this. GET /api/servers/all

@Summary		Get all servers snapshot
@Description	Returns the composite servers snapshot: WireGuard servers list, managed server, stats and WAN IP.
@Tags			servers
@Produce		json
@Security		CookieAuth
@Success		200	{object}	ServersAllResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/servers/all [get]

func (*ServersHandler) List

List returns all server WireGuard interfaces (built-in VPN Server + user-marked). GET /api/servers

func (*ServersHandler) Mark

Mark handles mark/unmark operations for server interfaces. POST /api/servers/mark?name=Wireguard0 — mark as server DELETE /api/servers/mark?name=Wireguard0 — unmark (return to tunnels) Both return the fresh ServersSnapshot as body.

@Summary		Mark/unmark interface as server
@Description	POST marks the named WG interface as a server (visible under Servers, hidden from Tunnels). DELETE unmarks (returns it to the Tunnels list). Both return the fresh ServersSnapshot.
@Tags			servers
@Produce		json
@Security		CookieAuth
@Param			name	query		string	true	"Interface name (e.g. Wireguard0)"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/mark [post]
@Router			/servers/mark [delete]

func (*ServersHandler) Marked

func (h *ServersHandler) Marked(w http.ResponseWriter, r *http.Request)

Marked returns the list of server interface IDs. GET /api/servers/marked

@Summary		Get marked server interfaces
@Description	Returns the list of interface IDs that have been marked as servers.
@Tags			servers
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/servers/marked [get]

func (*ServersHandler) PublishServerSnapshot

func (h *ServersHandler) PublishServerSnapshot(ctx context.Context)

PublishServerSnapshot broadcasts a resource:invalidated hint. Kept as a method on *ServersHandler because ndms/metrics.Poller calls it through the ServerSnapshotPublisher interface.

func (*ServersHandler) Restart

func (h *ServersHandler) Restart(w http.ResponseWriter, r *http.Request)

Restart accepts a restart/start command for a built-in/marked WireGuard server. POST /api/servers/restart?name=Wireguard0

@Summary		Restart or start WireGuard server
@Description	If the server interface is up, restarts it with down -> pause -> up. If it is down, starts it. The command is accepted quickly and executed in background so a client connected through this server does not cancel the operation.
@Tags			servers
@Produce		json
@Security		CookieAuth
@Param			name	query	string	true	"Interface name"
@Success		200		{object}	APIEnvelope
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Router			/servers/restart [post]

func (*ServersHandler) ServerPeerConf

func (h *ServersHandler) ServerPeerConf(w http.ResponseWriter, r *http.Request, name, pubkey string)

ServerPeerConf returns the downloadable WireGuard .conf for a peer. GET /api/servers/{name}/peers/{pubkey}/conf

@Summary		Get server peer config
@Description	Generates the WireGuard client .conf for the peer. Only available when the peer's private key was created via AWG Manager and is stored locally.
@Tags			servers
@Produce		json
@Security		CookieAuth
@Param			name	path		string	true	"Interface name (e.g. Wireguard0)"
@Param			pubkey	path		string	true	"Peer public key"
@Success		200		{object}	map[string]string
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/{name}/peers/{pubkey}/conf [get]

func (*ServersHandler) SetCommands

func (h *ServersHandler) SetCommands(commands *ndmscommand.Commands)

SetCommands wires NDMS interface commands used by server up/down/restart controls. Kept as a setter so tests using NewServersHandler do not need to construct the full command registry.

func (*ServersHandler) SetEnabled

func (h *ServersHandler) SetEnabled(w http.ResponseWriter, r *http.Request)

SetEnabled enables or disables a built-in/marked WireGuard server interface. POST /api/servers/enabled?name=Wireguard0

@Summary		Toggle WireGuard server enabled state
@Description	Brings a built-in or marked WireGuard server interface up or down.
@Tags			servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			name	query	string				true	"Interface name"
@Param			body	body	serverEnabledRequest	true	"Enabled flag"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Router			/servers/enabled [post]

func (*ServersHandler) SetEndpoint

func (h *ServersHandler) SetEndpoint(w http.ResponseWriter, r *http.Request, name string)

SetEndpoint stores the connect host used in generated client .conf files. POST /api/servers/{name}/endpoint

@Summary		Set server client endpoint host
@Description	Stores the IP or domain embedded in generated client configs. Empty clears the override and falls back to WAN IP. Returns the fresh servers snapshot.
@Tags			servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			name	path		string					true	"Interface name (e.g. Wireguard0)"
@Param			body	body		SetServerEndpointRequest	true	"Endpoint host"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/{name}/endpoint [post]

func (*ServersHandler) SetEventBus

func (h *ServersHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus used for SSE publishing.

func (*ServersHandler) SetManagedHandler

func (h *ServersHandler) SetManagedHandler(m *ManagedServerHandler)

SetManagedHandler sets the managed server handler for shared publishing.

func (*ServersHandler) SetManagedService

func (h *ServersHandler) SetManagedService(svc *managed.Service)

SetManagedService wires managed.Service for system-server NAT/policy RCI.

func (*ServersHandler) SetNAT

func (h *ServersHandler) SetNAT(w http.ResponseWriter, r *http.Request, name string)

SetNAT sets the NAT mode on a built-in/marked WireGuard server. POST /api/servers/{name}/nat

@Summary		Set server NAT mode
@Description	Sets the NAT mode (full / internet-only / none) on the named WireGuard server via NDMS. Returns the fresh servers snapshot.
@Tags			servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			name	path		string				true	"Interface name (e.g. Wireguard0)"
@Param			body	body		SetNATModeRequest	true	"NAT mode"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/{name}/nat [post]

func (*ServersHandler) SetPolicy

func (h *ServersHandler) SetPolicy(w http.ResponseWriter, r *http.Request, name string)

SetPolicy sets the ip hotspot policy on a built-in/marked WireGuard server. POST /api/servers/{name}/policy

@Summary		Set server access policy
@Description	Binds the named WireGuard server interface to an NDMS hotspot access policy (or "none"). Returns the fresh servers snapshot.
@Tags			servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			name	path		string					true	"Interface name (e.g. Wireguard0)"
@Param			body	body		SetServerPolicyRequest	true	"Policy id (router-side) or 'none'"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/{name}/policy [post]

func (*ServersHandler) Subtree

func (h *ServersHandler) Subtree(w http.ResponseWriter, r *http.Request)

Subtree dispatches /api/servers/{name}/... operations.

func (*ServersHandler) ToggleServerPeer

func (h *ServersHandler) ToggleServerPeer(w http.ResponseWriter, r *http.Request, name, pubkey string)

ToggleServerPeer enables or disables a peer. POST /api/servers/{name}/peers/{pubkey}/toggle

@Summary		Toggle server peer
@Description	Enables or disables (connect on/off) the peer on the named WireGuard server. Returns the fresh servers snapshot.
@Tags			servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			name	path		string					true	"Interface name (e.g. Wireguard0)"
@Param			pubkey	path		string					true	"Peer public key"
@Param			body	body		EnabledToggleRequest	true	"Enabled flag"
@Success		200		{object}	ServersAllResponse
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/{name}/peers/{pubkey}/toggle [post]

func (*ServersHandler) UpdateServerPeer

func (h *ServersHandler) UpdateServerPeer(w http.ResponseWriter, r *http.Request, name, pubkey string)

UpdateServerPeer updates a peer's tunnel IP and/or description. PUT /api/servers/{name}/peers/{pubkey}

@Summary		Update server peer
@Description	Changes a peer's allowed-IP and/or description on the named WireGuard server. Returns the fresh servers snapshot.
@Tags			servers
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			name	path		string							true	"Interface name (e.g. Wireguard0)"
@Param			pubkey	path		string							true	"Peer public key"
@Param			body	body		ServerUpdatePeerRequestDTO	true	"New description and tunnel IP"
@Success		200		{object}	ServersAllResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/servers/{name}/peers/{pubkey} [put]

func (*ServersHandler) WANIP

func (h *ServersHandler) WANIP(w http.ResponseWriter, r *http.Request)

WANIP returns the external WAN IP for .conf generation. GET /api/servers/wan-ip

@Summary		Get WAN IP
@Description	Returns the router's external WAN IP address.
@Tags			servers
@Produce		json
@Security		CookieAuth
@Success		200	{object}	WANIPResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/servers/wan-ip [get]

type SetDescriptionRequest

type SetDescriptionRequest struct {
	Name        string `json:"name" example:"Policy0"`
	Description string `json:"description" example:"My VPN policy"`
}

SetDescriptionRequest is the body for POST /access-policies/description.

type SetInterfaceUpRequest

type SetInterfaceUpRequest struct {
	Name string `json:"name" example:"Wireguard0"`
	Up   bool   `json:"up" example:"true"`
}

SetInterfaceUpRequest is the body for POST /access-policies/interface-up.

type SetLANSegmentsRequest

type SetLANSegmentsRequest struct {
	Segments []string `json:"segments"`
}

SetLANSegmentsRequest is the body for POST /managed-servers/{id}/lan-segments.

type SetNATModeRequest

type SetNATModeRequest struct {
	Mode    string `json:"mode,omitempty" example:"internet-only" enums:"full,internet-only,none"`
	Enabled *bool  `json:"enabled,omitempty" example:"true"` // backward-compat: если mode пуст
}

SetNATModeRequest is the body for POST /managed-servers/{id}/nat.

type SetPolicyOrderRequest

type SetPolicyOrderRequest struct {
	Order []string `json:"order" example:"default"`
}

SetPolicyOrderRequest is the body for POST /hydraroute/policy-order.

type SetServerEndpointRequest

type SetServerEndpointRequest struct {
	Endpoint string `json:"endpoint" example:"203.0.113.42"`
}

SetServerEndpointRequest is the body for POST /servers/{name}/endpoint.

type SetServerPolicyRequest

type SetServerPolicyRequest struct {
	Policy string `json:"policy" example:"Policy0"`
}

SetServerPolicyRequest is the body for POST /managed-servers/{id}/policy.

type SetStandaloneRequest

type SetStandaloneRequest struct {
	Name    string `json:"name" example:"Policy0"`
	Enabled bool   `json:"enabled" example:"true"`
}

SetStandaloneRequest is the body for POST /access-policies/standalone.

type SettingsData

type SettingsData struct {
	SchemaVersion             int                  `json:"schemaVersion" example:"16"`
	AuthEnabled               bool                 `json:"authEnabled" example:"false"`
	Server                    ServerSettingsDTO    `json:"server"`
	PingCheck                 PingCheckSettingsDTO `json:"pingCheck"`
	Logging                   LoggingSettingsDTO   `json:"logging"`
	MonitoringExcludedTunnels []string             `json:"monitoringExcludedTunnels,omitempty" example:"tn-1,sys-2"`
	DisableMemorySaving       bool                 `json:"disableMemorySaving" example:"false"`
	Updates                   UpdateSettingsDTO    `json:"updates"`
	Download                  DownloadSettingsDTO  `json:"download"`
	DnsRoute                  DNSRouteSettingsDTO  `json:"dnsRoute"`
	GeoFile                   GeoFileSettingsDTO   `json:"geoFile"`
	ConnectivityCheckURL      string               `json:"connectivityCheckUrl" example:"http://connectivitycheck.gstatic.com/generate_204"`
	// UsageLevel controls which UI sections are visible to the user.
	// Filtering is frontend-only — the API does not enforce it.
	// enums: expert,advanced,basic
	// First enum is the prism mock default (prism picks the first
	// enum value over the example tag); putting `expert` first means
	// dev:mock surfaces all advanced UI without manual toggling.
	UsageLevel string `json:"usageLevel" example:"expert" enums:"expert,advanced,basic"`
}

SettingsData is the payload for GET /settings/get.

type SettingsHandler

type SettingsHandler struct {
	// contains filtered or unexported fields
}

SettingsHandler handles settings API endpoints.

func NewSettingsHandler

func NewSettingsHandler(store *storage.SettingsStore, appLogger logging.AppLogger) *SettingsHandler

NewSettingsHandler creates a new settings handler.

func (*SettingsHandler) Get

Get returns current settings.

@Summary		Get settings
@Description	Returns the full Settings object (server, pingCheck, logging, dnsRoute, managed, apiKey, ...).
@Tags			settings
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SettingsResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/settings/get [get]

func (*SettingsHandler) RegenerateApiKey

func (h *SettingsHandler) RegenerateApiKey(w http.ResponseWriter, r *http.Request)

RegenerateApiKey generates a fresh UUID v4 server-side, persists it into Settings.ApiKey, and returns the updated Settings. Lives on the backend (not in browser via crypto.randomUUID) because the UI is served over plain HTTP and the WebCrypto API is unavailable in non-secure contexts.

@Summary		Regenerate API key
@Description	Generates a fresh UUID v4 via crypto/rand, stores it into Settings.ApiKey, and returns the updated Settings. The new key takes effect immediately as a `Authorization: Bearer <key>` substitute for the session cookie.
@Tags			settings
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SettingsResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/settings/regenerate-api-key [post]

func (*SettingsHandler) SetApplyLoggingSettings

func (h *SettingsHandler) SetApplyLoggingSettings(fn func())

SetApplyLoggingSettings sets the callback that re-applies logging settings to the live buffers (MaxAge, MaxEntries) after a successful settings update. Called once per Update with no arguments — the callback re-reads the settings store itself.

func (*SettingsHandler) SetApplySingboxLogSettings

func (h *SettingsHandler) SetApplySingboxLogSettings(fn func() error)

SetApplySingboxLogSettings sets callback that re-applies sing-box log-level into 00-base.json and triggers orchestrator-driven reload.

func (*SettingsHandler) SetDownloadService

func (h *SettingsHandler) SetDownloadService(svc *downloader.Service)

func (*SettingsHandler) SetEventBus

func (h *SettingsHandler) SetEventBus(bus *events.Bus)

SetEventBus wires the SSE bus so settings mutations broadcast a resource:invalidated hint to all connected clients.

func (*SettingsHandler) SetLogsSnapshot

func (h *SettingsHandler) SetLogsSnapshot(fn func())

SetLogsSnapshot sets the function that publishes a logs snapshot.

func (*SettingsHandler) SetMonitoringService

func (h *SettingsHandler) SetMonitoringService(svc MonitoringRefreshService)

SetMonitoringService sets the monitoring service for forced snapshot refresh after settings changes that affect matrix composition.

func (*SettingsHandler) SetPingCheckService

func (h *SettingsHandler) SetPingCheckService(svc PingCheckToggleService)

SetPingCheckService sets the ping check service for toggle operations.

func (*SettingsHandler) SetPingCheckSnapshot

func (h *SettingsHandler) SetPingCheckSnapshot(fn func())

SetPingCheckSnapshot sets the function that publishes a pingcheck snapshot.

func (*SettingsHandler) SetTunnelStore

func (h *SettingsHandler) SetTunnelStore(tunnels *storage.AWGTunnelStore)

SetTunnelStore sets the tunnel store for ping check toggle logic.

func (*SettingsHandler) Update

func (h *SettingsHandler) Update(w http.ResponseWriter, r *http.Request)

Update saves settings.

@Summary		Update settings
@Description	Persists Settings via patch semantics: any field omitted from the payload is preserved, including top-level bool flags. Send only the fields you want to change, or send the full Settings object to update everything atomically. ApiKey preserved when omitted (rotate via /settings/regenerate-api-key).
@Tags			settings
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SettingsData	true	"Settings patch — any subset of fields"
@Success		200		{object}	SettingsResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/settings/update [post]

type SettingsProvider

type SettingsProvider interface {
	Get() (*storage.Settings, error)
}

SettingsProvider provides access to settings.

type SettingsResponse

type SettingsResponse struct {
	Success bool         `json:"success" example:"true"`
	Data    SettingsData `json:"data"`
}

SettingsResponse is the envelope for GET /settings/get.

type SignatureCaptureData

type SignatureCaptureData struct {
	OK      bool                `json:"ok" example:"true"`
	Source  string              `json:"source" example:"Wireguard0"`
	Packets SignaturePacketsDTO `json:"packets"`
	Warning string              `json:"warning,omitempty" example:""`
}

SignatureCaptureData mirrors frontend SignatureCaptureResult.

type SignatureCaptureResponse

type SignatureCaptureResponse struct {
	Success bool                 `json:"success" example:"true"`
	Data    SignatureCaptureData `json:"data"`
}

SignatureCaptureResponse is the envelope for GET /signature/capture.

type SignatureHandler

type SignatureHandler struct{}

func NewSignatureHandler

func NewSignatureHandler() *SignatureHandler

func (*SignatureHandler) Capture

func (h *SignatureHandler) Capture(w http.ResponseWriter, r *http.Request)

Capture runs TLS certificate capture for a domain.

@Summary		Signature capture
@Tags			signature
@Produce		json
@Security		CookieAuth
@Param			domain	query	string	true	"Domain name"
@Success		200	{object}	SignatureCaptureResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/signature/capture [get]

type SignaturePacketsDTO

type SignaturePacketsDTO struct {
	I1 string `json:"i1" example:"0a1b2c3d"`
	I2 string `json:"i2" example:"4e5f6a7b"`
	I3 string `json:"i3" example:"8c9d0e1f"`
	I4 string `json:"i4" example:"2a3b4c5d"`
	I5 string `json:"i5" example:"6e7f8a9b"`
}

SignaturePacketsDTO is the packets field in SignatureCaptureResult.

type SingboxConfigHandler

type SingboxConfigHandler struct {
	// contains filtered or unexported fields
}

SingboxConfigHandler exposes a read-only preview of the merged sing-box configuration. The orchestrator's ConfigDir is injected via configDirFn so tests can swap a tmp dir without dragging in the orchestrator.

func NewSingboxConfigHandler

func NewSingboxConfigHandler(configDirFn func() string) *SingboxConfigHandler

NewSingboxConfigHandler constructs the handler.

func (*SingboxConfigHandler) Preview

Preview returns the read-only merged sing-box configuration.

@Summary		Get merged sing-box configuration preview
@Description	Returns the read-only pretty-printed JSON that sing-box loads when started with `-C config.d/` — every active slot file concatenated in lexicographic order, with arrays merged and tag collisions surfaced as errors.
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Success		200	{object}	OkResponse{data=SingboxConfigPreviewResponse}
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/config-preview [get]

type SingboxConfigPreviewResponse

type SingboxConfigPreviewResponse struct {
	JSON string `json:"json"`
}

SingboxConfigPreviewResponse is the typed payload of GET /api/singbox/config-preview. The JSON field is a pretty-printed merge of every active config.d/*.json — exactly what sing-box loads when started with `-C config.d/`.

type SingboxConnectionsClientsData

type SingboxConnectionsClientsData struct {
	ClientsByIP map[string]string `json:"clientsByIP"`
}

SingboxConnectionsClientsData mirrors frontend ClientsByIP map.

type SingboxConnectionsClientsResponse

type SingboxConnectionsClientsResponse struct {
	Success bool                          `json:"success" example:"true"`
	Data    SingboxConnectionsClientsData `json:"data"`
}

SingboxConnectionsClientsResponse is the envelope for GET /singbox/connections/clients.

type SingboxConnectionsHandler

type SingboxConnectionsHandler struct {
	// contains filtered or unexported fields
}

SingboxConnectionsHandler serves the narrow IP→display-name lookup used by the Connections monitor sub-tab to enrich Clash connection rows.

func NewSingboxConnectionsHandler

func NewSingboxConnectionsHandler(hotspot HotspotLister) *SingboxConnectionsHandler

NewSingboxConnectionsHandler builds the handler. hotspot may be nil during partial bootstrap — handler responds 503 until wired.

func (*SingboxConnectionsHandler) Clients

Clients returns IP→display-name from the NDMS hotspot cache.

@Summary		IP → display-name map for sing-box connections
@Description	Returns the current NDMS hotspot mapping of source IPs to device display names. Cached upstream (TTL 30s). On any error returns 200 with an empty map (best-effort) so the UI keeps working with raw IPs.
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxConnectionsClientsResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		503	{object}	APIErrorEnvelope
@Router			/singbox/connections/clients [get]

type SingboxControlRequest

type SingboxControlRequest struct {
	Action string `json:"action" example:"start" enums:"start,stop,restart"`
}

SingboxControlRequest is the body for POST /singbox/control.

type SingboxDNSGlobalsData

type SingboxDNSGlobalsData struct {
	Final    string `json:"final" example:"cloudflare"`
	Strategy string `json:"strategy" example:"prefer_ipv4"`
}

SingboxDNSGlobalsData carries the global DNS settings exposed by GET/PUT /singbox/router/dns/globals. Reused as the request body type.

type SingboxDNSGlobalsResponse

type SingboxDNSGlobalsResponse struct {
	Success bool                  `json:"success" example:"true"`
	Data    SingboxDNSGlobalsData `json:"data"`
}

SingboxDNSGlobalsResponse is the envelope for GET/PUT /singbox/router/dns/globals.

type SingboxDNSRewriteDTO

type SingboxDNSRewriteDTO struct {
	Pattern string   `json:"pattern" example:"finland10*.discord.media"`
	IPs     []string `json:"ips" example:"104.25.158.178"`
}

SingboxDNSRewriteDTO mirrors dnsrewrite.DNSRewrite — a glob domain pattern rewritten to one or more IPs.

type SingboxDNSRewriteDeleteRequest

type SingboxDNSRewriteDeleteRequest struct {
	Index int `json:"index" example:"0"`
}

SingboxDNSRewriteDeleteRequest is the body for POST /singbox/router/dns/rewrites/delete.

type SingboxDNSRewriteMoveRequest

type SingboxDNSRewriteMoveRequest struct {
	From int `json:"from" example:"3"`
	To   int `json:"to" example:"0"`
}

SingboxDNSRewriteMoveRequest is the body for POST /singbox/router/dns/rewrites/move.

type SingboxDNSRewriteUpdateRequest

type SingboxDNSRewriteUpdateRequest struct {
	Index   int                  `json:"index" example:"0"`
	Rewrite SingboxDNSRewriteDTO `json:"rewrite"`
}

SingboxDNSRewriteUpdateRequest is the body for POST /singbox/router/dns/rewrites/update.

type SingboxDNSRewritesListResponse

type SingboxDNSRewritesListResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    []SingboxDNSRewriteDTO `json:"data"`
}

SingboxDNSRewritesListResponse is the envelope for GET /singbox/router/dns/rewrites/list.

type SingboxDNSRuleDTO

type SingboxDNSRuleDTO struct {
	RuleSet       []string `json:"rule_set,omitempty" example:"geosite-cn"`
	DomainSuffix  []string `json:"domain_suffix,omitempty" example:".example.com"`
	Domain        []string `json:"domain,omitempty" example:"example.com"`
	DomainKeyword []string `json:"domain_keyword,omitempty" example:"google"`
	QueryType     []string `json:"query_type,omitempty" example:"A"`
	Server        string   `json:"server,omitempty" example:"cloudflare"`
	Action        string   `json:"action,omitempty" example:"route"`
}

SingboxDNSRuleDTO mirrors router.DNSRule.

type SingboxDNSRuleDeleteRequest

type SingboxDNSRuleDeleteRequest struct {
	Index int `json:"index" example:"0"`
}

SingboxDNSRuleDeleteRequest is the body for POST /singbox/router/dns/rules/delete.

type SingboxDNSRuleMoveRequest

type SingboxDNSRuleMoveRequest struct {
	From int `json:"from" example:"3"`
	To   int `json:"to" example:"0"`
}

SingboxDNSRuleMoveRequest is the body for POST /singbox/router/dns/rules/move.

type SingboxDNSRuleUpdateRequest

type SingboxDNSRuleUpdateRequest struct {
	Index int               `json:"index" example:"0"`
	Rule  SingboxDNSRuleDTO `json:"rule"`
}

SingboxDNSRuleUpdateRequest is the body for POST /singbox/router/dns/rules/update.

type SingboxDNSRulesListResponse

type SingboxDNSRulesListResponse struct {
	Success bool                `json:"success" example:"true"`
	Data    []SingboxDNSRuleDTO `json:"data"`
}

SingboxDNSRulesListResponse is the envelope for GET /singbox/router/dns/rules/list.

type SingboxDNSServerDTO

type SingboxDNSServerDTO struct {
	Tag            string                    `json:"tag" example:"cloudflare"`
	Type           string                    `json:"type" example:"udp"`
	Server         string                    `json:"server" example:"1.1.1.1"`
	ServerPort     int                       `json:"server_port,omitempty" example:"53"`
	Path           string                    `json:"path,omitempty" example:"/dns-query"`
	Detour         string                    `json:"detour,omitempty" example:"direct"`
	Strategy       string                    `json:"domain_strategy,omitempty" example:"prefer_ipv4"`
	DomainResolver *SingboxDomainResolverDTO `json:"domain_resolver,omitempty"`
}

SingboxDNSServerDTO mirrors router.DNSServer.

type SingboxDNSServerDeleteRequest

type SingboxDNSServerDeleteRequest struct {
	Tag   string `json:"tag" example:"cloudflare"`
	Force bool   `json:"force" example:"false"`
}

SingboxDNSServerDeleteRequest is the body for POST /singbox/router/dns/servers/delete. force=true overrides the "still referenced by a rule" guard.

type SingboxDNSServerMoveRequest

type SingboxDNSServerMoveRequest struct {
	From int `json:"from" example:"3"`
	To   int `json:"to" example:"0"`
}

SingboxDNSServerMoveRequest is the body for POST /singbox/router/dns/servers/move.

type SingboxDNSServerUpdateRequest

type SingboxDNSServerUpdateRequest struct {
	Tag    string              `json:"tag" example:"cloudflare"`
	Server SingboxDNSServerDTO `json:"server"`
}

SingboxDNSServerUpdateRequest is the body for POST /singbox/router/dns/servers/update.

type SingboxDNSServersListResponse

type SingboxDNSServersListResponse struct {
	Success bool                  `json:"success" example:"true"`
	Data    []SingboxDNSServerDTO `json:"data"`
}

SingboxDNSServersListResponse is the envelope for GET /singbox/router/dns/servers/list.

type SingboxDomainResolverDTO

type SingboxDomainResolverDTO struct {
	Server   string `json:"server,omitempty" example:"local"`
	Strategy string `json:"strategy,omitempty" example:"ipv4_only"`
}

SingboxDomainResolverDTO mirrors router.DomainResolver, the optional nested resolver descriptor on a DNS server.

type SingboxFakeIPConfigHandler

type SingboxFakeIPConfigHandler struct {
	// contains filtered or unexported fields
}

SingboxFakeIPConfigHandler exposes the FakeIPConfigService CRUD surface (SlotFakeIP) as REST endpoints under /api/singbox/fakeip/config/... It is a pure mechanical mirror of SingboxRouterHandler: same DTO shapes, same error mapping, same nil-array guards, different path prefix and FakeIP-prefixed service calls.

func NewSingboxFakeIPConfigHandler

func NewSingboxFakeIPConfigHandler(svc router.FakeIPConfigService, appLogger logging.AppLogger) *SingboxFakeIPConfigHandler

NewSingboxFakeIPConfigHandler constructs a handler backed by svc. appLogger may be nil — the scoped logger is nil-safe.

func (*SingboxFakeIPConfigHandler) AddDNSRule

AddDNSRule appends a new DNS routing rule to the fakeip-tun slot.

@Summary		Add fakeip-config DNS rule
@Description	Appends a new DNS routing rule. The rule's server tag must already exist (fakeip-tun slot).
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRuleDTO	true	"DNS routing rule"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/rules/add [post]

func (*SingboxFakeIPConfigHandler) AddDNSServer

AddDNSServer registers a new DNS upstream in the fakeip-tun slot.

@Summary		Add fakeip-config DNS server
@Description	Registers a new DNS upstream (tag must be unique) in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSServerDTO	true	"DNS server descriptor"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/servers/add [post]

func (*SingboxFakeIPConfigHandler) AddOutbound

AddOutbound creates a new composite outbound in the fakeip-tun slot.

@Summary		Add fakeip-config outbound
@Description	Creates a new composite outbound in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterOutboundDTO	true	"Composite outbound payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/outbounds/add [post]

func (*SingboxFakeIPConfigHandler) AddRule

AddRule appends a new routing rule to the fakeip-tun slot.

@Summary		Add fakeip-config route rule
@Description	Appends a new routing rule to the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleDTO	true	"Routing rule payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rules/add [post]

func (*SingboxFakeIPConfigHandler) AddRuleSet

AddRuleSet registers a new ruleset in the fakeip-tun slot.

@Summary		Add fakeip-config ruleset
@Description	Registers a new ruleset in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleSetDTO	true	"RuleSet payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rulesets/add [post]

func (*SingboxFakeIPConfigHandler) DeleteDNSRule

func (h *SingboxFakeIPConfigHandler) DeleteDNSRule(w http.ResponseWriter, r *http.Request)

DeleteDNSRule removes the DNS rule at the given index (fakeip-tun slot).

@Summary		Delete fakeip-config DNS rule
@Description	Removes the DNS rule at the given index (0-based priority slot) from the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRuleDeleteRequest	true	"Index of the rule to remove"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/rules/delete [post]

func (*SingboxFakeIPConfigHandler) DeleteDNSServer

func (h *SingboxFakeIPConfigHandler) DeleteDNSServer(w http.ResponseWriter, r *http.Request)

DeleteDNSServer removes the DNS upstream identified by tag from the fakeip-tun slot.

@Summary		Delete fakeip-config DNS server
@Description	Removes the DNS upstream identified by tag. Engine-locked servers ("real", fakeip-type) are rejected with 400. Pass force=true to bypass the reference guard.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSServerDeleteRequest	true	"Tag + optional force flag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		409		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/servers/delete [post]

func (*SingboxFakeIPConfigHandler) DeleteOutbound

func (h *SingboxFakeIPConfigHandler) DeleteOutbound(w http.ResponseWriter, r *http.Request)

DeleteOutbound removes the composite outbound identified by tag (fakeip-tun slot).

@Summary		Delete fakeip-config outbound
@Description	Removes the composite outbound identified by tag from the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterOutboundDeleteRequest	true	"Tag + optional force flag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		409		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/outbounds/delete [post]

func (*SingboxFakeIPConfigHandler) DeleteRule

DeleteRule removes the route rule at the given index (fakeip-tun slot).

@Summary		Delete fakeip-config route rule
@Description	Removes the route rule at the given index (0-based) from the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleDeleteRequest	true	"Index of the rule to remove"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rules/delete [post]

func (*SingboxFakeIPConfigHandler) DeleteRuleSet

func (h *SingboxFakeIPConfigHandler) DeleteRuleSet(w http.ResponseWriter, r *http.Request)

DeleteRuleSet removes the ruleset identified by tag from the fakeip-tun slot.

@Summary		Delete fakeip-config ruleset
@Description	Removes the ruleset identified by tag from the fakeip-tun slot. Refuses if referenced; pass force=true to override.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleSetDeleteRequest	true	"Tag + optional force flag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		409		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rulesets/delete [post]

func (*SingboxFakeIPConfigHandler) GetDNSGlobals

func (h *SingboxFakeIPConfigHandler) GetDNSGlobals(w http.ResponseWriter, r *http.Request)

GetDNSGlobals returns the global DNS final/strategy fields (fakeip-tun slot).

@Summary		Get fakeip-config DNS globals
@Description	Returns the global DNS settings (final, strategy) for the fakeip-tun slot.
@Tags			singbox-fakeip
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxDNSGlobalsResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/globals [get]

func (*SingboxFakeIPConfigHandler) ListDNSRules

ListDNSRules returns all DNS routing rules in priority order (fakeip-tun slot).

@Summary		List fakeip-config DNS rules
@Description	Returns all DNS routing rules in priority (top-first) order for the fakeip-tun slot. Always a JSON array, never null.
@Tags			singbox-fakeip
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxDNSRulesListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/rules/list [get]

func (*SingboxFakeIPConfigHandler) ListDNSServers

func (h *SingboxFakeIPConfigHandler) ListDNSServers(w http.ResponseWriter, r *http.Request)

ListDNSServers returns all configured DNS servers in the fakeip-tun slot.

@Summary		List fakeip-config DNS servers
@Description	Returns all configured DNS upstreams for the fakeip-tun slot. Always a JSON array, never null.
@Tags			singbox-fakeip
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxDNSServersListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/servers/list [get]

func (*SingboxFakeIPConfigHandler) ListOutbounds

func (h *SingboxFakeIPConfigHandler) ListOutbounds(w http.ResponseWriter, r *http.Request)

ListOutbounds returns all composite outbounds in the fakeip-tun slot.

@Summary		List fakeip-config outbounds
@Description	Returns all composite outbounds (selectors/urltests) in the fakeip-tun slot.
@Tags			singbox-fakeip
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterOutboundsListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/outbounds/list [get]

func (*SingboxFakeIPConfigHandler) ListRuleSets

ListRuleSets returns all configured rulesets in the fakeip-tun slot.

@Summary		List fakeip-config rulesets
@Description	Returns all configured rulesets in the fakeip-tun slot.
@Tags			singbox-fakeip
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterRuleSetsListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rulesets/list [get]

func (*SingboxFakeIPConfigHandler) ListRules

ListRules returns all routing rules in priority order (fakeip-tun slot).

@Summary		List fakeip-config route rules
@Description	Returns all routing rules in priority (top-first) order for the fakeip-tun slot.
@Tags			singbox-fakeip
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterRulesListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rules/list [get]

func (*SingboxFakeIPConfigHandler) MoveDNSRule

MoveDNSRule moves a DNS rule from one priority slot to another (fakeip-tun slot).

@Summary		Move fakeip-config DNS rule
@Description	Moves the DNS rule from index `from` to index `to` (both 0-based) in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRuleMoveRequest	true	"From-index and to-index"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/rules/move [post]

func (*SingboxFakeIPConfigHandler) MoveDNSServer

func (h *SingboxFakeIPConfigHandler) MoveDNSServer(w http.ResponseWriter, r *http.Request)

MoveDNSServer reorders a DNS server from one slot to another.

@Summary		Move fakeip-config DNS server
@Description	Moves the DNS server from index `from` to index `to` (both 0-based) in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSServerMoveRequest	true	"From-index and to-index"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/servers/move [post]

func (*SingboxFakeIPConfigHandler) MoveRule

MoveRule moves a route rule from one priority slot to another (fakeip-tun slot).

@Summary		Move fakeip-config route rule
@Description	Moves the route rule from index `from` to index `to` (both 0-based) in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleMoveRequest	true	"From-index and to-index"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rules/move [post]

func (*SingboxFakeIPConfigHandler) PutDNSGlobals

func (h *SingboxFakeIPConfigHandler) PutDNSGlobals(w http.ResponseWriter, r *http.Request)

PutDNSGlobals persists global DNS final/strategy fields (fakeip-tun slot).

@Summary		Update fakeip-config DNS globals
@Description	Persists the global DNS settings (final, strategy) for the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSGlobalsData	true	"final + strategy"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/globals [post]
@Router			/singbox/fakeip/config/dns/globals [put]

func (*SingboxFakeIPConfigHandler) SetRouteFinal

func (h *SingboxFakeIPConfigHandler) SetRouteFinal(w http.ResponseWriter, r *http.Request)

SetRouteFinal sets route.final on the fakeip-tun slot.

@Summary		Set fakeip-config route.final
@Description	Sets route.final on the fakeip-tun slot. The tag must reference a known outbound.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRouteFinalRequest	true	"New final outbound tag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/route/final [post]

func (*SingboxFakeIPConfigHandler) UpdateDNSRule

func (h *SingboxFakeIPConfigHandler) UpdateDNSRule(w http.ResponseWriter, r *http.Request)

UpdateDNSRule replaces the DNS rule at the given index (fakeip-tun slot).

@Summary		Update fakeip-config DNS rule
@Description	Replaces the DNS rule at the given index (0-based priority slot) in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRuleUpdateRequest	true	"Index + replacement rule"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/rules/update [post]

func (*SingboxFakeIPConfigHandler) UpdateDNSServer

func (h *SingboxFakeIPConfigHandler) UpdateDNSServer(w http.ResponseWriter, r *http.Request)

UpdateDNSServer replaces the DNS upstream identified by tag in the fakeip-tun slot.

@Summary		Update fakeip-config DNS server
@Description	Replaces the DNS upstream identified by tag with the provided one (fakeip-tun slot).
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSServerUpdateRequest	true	"Tag + replacement server"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/dns/servers/update [post]

func (*SingboxFakeIPConfigHandler) UpdateOutbound

func (h *SingboxFakeIPConfigHandler) UpdateOutbound(w http.ResponseWriter, r *http.Request)

UpdateOutbound replaces the composite outbound identified by tag (fakeip-tun slot).

@Summary		Update fakeip-config outbound
@Description	Replaces the composite outbound identified by tag in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterOutboundUpdateRequest	true	"Tag + replacement outbound"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/outbounds/update [post]

func (*SingboxFakeIPConfigHandler) UpdateRule

UpdateRule replaces a route rule at the given index (fakeip-tun slot).

@Summary		Update fakeip-config route rule
@Description	Replaces the route rule at index with the provided one (fakeip-tun slot).
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleUpdateRequest	true	"Index + replacement rule"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rules/update [post]

func (*SingboxFakeIPConfigHandler) UpdateRuleSet

func (h *SingboxFakeIPConfigHandler) UpdateRuleSet(w http.ResponseWriter, r *http.Request)

UpdateRuleSet replaces the ruleset identified by tag in the fakeip-tun slot.

@Summary		Update fakeip-config ruleset
@Description	Replaces the ruleset identified by tag in the fakeip-tun slot.
@Tags			singbox-fakeip
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleSetUpdateRequest	true	"Tag + new RuleSet payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/fakeip/config/rulesets/update [post]

type SingboxHandler

type SingboxHandler struct {
	// contains filtered or unexported fields
}

SingboxHandler serves /api/singbox/* routes.

func NewSingboxHandler

func NewSingboxHandler(op *singbox.Operator, bus *events.Bus, dc *singbox.DelayChecker, ts *testing.Service, appLogger ...logging.AppLogger) *SingboxHandler

NewSingboxHandler creates a new singbox handler.

func (*SingboxHandler) AddTunnels

func (h *SingboxHandler) AddTunnels(w http.ResponseWriter, r *http.Request)

AddTunnels handles POST /api/singbox/tunnels. Body: {"links": "vless://...\nhy2://..."}. Returns imported tunnels and per-line errors.

@Summary		Add sing-box tunnel(s)
@Tags			singbox
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/tunnels [post]

func (*SingboxHandler) CheckConnectivity

func (h *SingboxHandler) CheckConnectivity(w http.ResponseWriter, r *http.Request)

CheckConnectivity performs connectivity test through a sing-box tunnel.

@Summary		Sing-box tunnel connectivity test
@Description	Tests connectivity through a sing-box tunnel. Provide either `tag` (resolved to tunnel kernel interface) or `iface` (direct kernel interface override, useful for subscription tests).
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Param			tag		query		string	false	"Tunnel tag (required when iface is not set)"
@Param			iface	query		string	false	"Kernel interface override (e.g. t2s12)"
@Success		200		{object}	APIEnvelope
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/tunnels/test/connectivity [get]

func (*SingboxHandler) CheckIP

func (h *SingboxHandler) CheckIP(w http.ResponseWriter, r *http.Request)

CheckIP tests IP through a sing-box tunnel.

@Summary		Sing-box tunnel IP test
@Description	Resolves current external IP through a sing-box tunnel. Provide either `tag` (resolved to tunnel kernel interface) or `iface` (direct kernel interface override, useful for subscription tests). Optional `service` overrides IP-check endpoint.
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Param			tag		query		string	false	"Tunnel tag (required when iface is not set)"
@Param			iface	query		string	false	"Kernel interface override (e.g. t2s12)"
@Param			service	query		string	false	"Custom IP-check service URL"
@Success		200		{object}	APIEnvelope
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/tunnels/test/ip [get]

func (*SingboxHandler) Control

func (h *SingboxHandler) Control(w http.ResponseWriter, r *http.Request)

Control handles POST /api/singbox/control. Body: {"action": "start"|"stop"|"restart"}. Returns the fresh status so the client can update its cache. Mirrors the shape of /api/system/hydraroute-control.

@Summary		Control sing-box process
@Description	Starts, stops, or restarts the sing-box engine. Returns the fresh status snapshot. Mirrors /system/hydraroute-control.
@Tags			singbox
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxControlRequest	true	"Action to perform"
@Success		200		{object}	SingboxStatusResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/control [post]

func (*SingboxHandler) DelayCheck

func (h *SingboxHandler) DelayCheck(w http.ResponseWriter, r *http.Request)

DelayCheck handles POST /api/singbox/tunnels/delay-check?tag=X.

@Summary		Sing-box delay check
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Param			tag	query	string	true	"Tunnel tag"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/tunnels/delay-check [post]

func (*SingboxHandler) DeleteTunnel

func (h *SingboxHandler) DeleteTunnel(w http.ResponseWriter, r *http.Request)

DeleteTunnel handles DELETE /api/singbox/tunnels?tag={tag}.

@Summary		Delete sing-box tunnel
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/tunnels [delete]
func (h *SingboxHandler) ExportShareLink(w http.ResponseWriter, r *http.Request)

ExportShareLink handles POST /api/singbox/tunnels/share-link. Body: {"outbound":{...},"label":"optional fragment"}.

@Summary		Export sing-box tunnel as share-link
@Tags			singbox
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/tunnels/share-link [post]

func (*SingboxHandler) GetTunnel

func (h *SingboxHandler) GetTunnel(w http.ResponseWriter, r *http.Request)

GetTunnel handles GET /api/singbox/tunnels?tag={tag}.

func (*SingboxHandler) Install

func (h *SingboxHandler) Install(w http.ResponseWriter, r *http.Request)

Install handles POST /api/singbox/install. Returns the fresh status so the client can update cache without refetch. Also publishes a resource:invalidated hint so other tabs/subscribers refresh.

@Summary		Install sing-box
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/install [post]

func (*SingboxHandler) ListTunnels

func (h *SingboxHandler) ListTunnels(w http.ResponseWriter, r *http.Request)

ListTunnels handles GET /api/singbox/tunnels. Returns all tunnels enriched with per-tunnel connectivity from the Clash API.

func (*SingboxHandler) RenameTunnel

func (h *SingboxHandler) RenameTunnel(w http.ResponseWriter, r *http.Request)

RenameTunnel handles PATCH /api/singbox/tunnels/rename. Body: {"oldTag":"old","newTag":"new"}.

func (*SingboxHandler) ServeGETTunnels

func (h *SingboxHandler) ServeGETTunnels(w http.ResponseWriter, r *http.Request)

ServeGETTunnels handles GET /api/singbox/tunnels: list all tunnels, or single tunnel when query tag is set.

@Summary		List or get sing-box tunnel(s)
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Param			tag	query	string	false	"When set, returns single tunnel"
@Success		200	{object}	SingboxTunnelsResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/tunnels [get]

func (*SingboxHandler) SetNDMSProxyMigrator

func (h *SingboxHandler) SetNDMSProxyMigrator(m *singbox.Migrator, settings ndmsProxyToggler)

SetNDMSProxyMigrator подключает мигратор и getter настроек после конструкции (избегаем circular construction между SettingsStore и SingboxHandler). Без них endpoint ToggleNDMSProxy возвращает 500.

func (*SingboxHandler) SetOutboundRefCheckers

SetOutboundRefCheckers wires device-proxy and router reference guards for sing-box tunnel deletion (refuse when tag is still in a composite/rule).

func (*SingboxHandler) SetSettingsStore

func (h *SingboxHandler) SetSettingsStore(settings *storage.SettingsStore)

SetSettingsStore wires global settings for connectivity checks.

func (*SingboxHandler) SpeedTestStream

func (h *SingboxHandler) SpeedTestStream(w http.ResponseWriter, r *http.Request)

SpeedTestStream handles GET /api/singbox/tunnels/test/speed/stream?tag=X&server=Y&port=Z. Runs download then upload sequentially, keyed by sing-box tunnel tag. Streams events via SSE: phase, interval, result, done, error.

Optional `iface` query param overrides the tag→interface resolution (subscription cards use it to test the composite NDMS Proxy interface directly). When NDMS Proxy is globally disabled the override is rejected with 412 PROXY_DISABLED — the t2sN/ProxyN composite interface no longer exists, so iperf against it would silently fail or hang.

@Summary		Sing-box tunnel speed test stream
@Tags			singbox
@Produce		text/event-stream
@Security		CookieAuth
@Param			tag		query	string	true	"Sing-box outbound tag"
@Param			server	query	string	true	"iperf3 server host"
@Param			port	query	int		true	"iperf3 server port"
@Param			iface	query	string	false	"Kernel interface override (NDMS Proxy must be enabled)"
@Success		200	{string}	string	"SSE stream"
@Failure		400	{object}	APIErrorEnvelope
@Failure		404	{object}	APIErrorEnvelope	"Tunnel tag not found"
@Failure		412	{object}	APIErrorEnvelope	"NDMS Proxy disabled — iface override unavailable"
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/tunnels/test/speed/stream [get]

func (*SingboxHandler) Status

func (h *SingboxHandler) Status(w http.ResponseWriter, r *http.Request)

Status handles GET /api/singbox/status.

@Summary		Sing-box status
@Tags			singbox
@Description	Available when sing-box integration is enabled in the build.
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxStatusResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/status [get]

func (*SingboxHandler) ToggleNDMSProxy

func (h *SingboxHandler) ToggleNDMSProxy(w http.ResponseWriter, r *http.Request)

ToggleNDMSProxy handles POST /api/singbox/ndms-proxy. Переключает создание NDMS Proxy интерфейсов для sing-box туннелей. Idempotent: повторный вызов с тем же значением — 200 OK без миграции. 412 при enabled=true если NDMS-компонент 'proxy' не установлен.

@Summary		Toggle NDMS Proxy creation for sing-box tunnels
@Tags			singbox
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		ToggleNDMSProxyRequest	true	"Toggle value"
@Success		200		{object}	APIEnvelope
@Failure		400		{object}	APIErrorEnvelope
@Failure		412		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/ndms-proxy [post]

func (*SingboxHandler) Update

func (h *SingboxHandler) Update(w http.ResponseWriter, r *http.Request)

Update handles POST /api/singbox/update. Replaces the installed managed sing-box binary with the version this awg-manager build is pinned to. No-op when versions match. Returns the fresh status so the client can clear its update prompt without a separate refetch.

@Summary		Update managed sing-box binary
@Description	Replaces the currently-installed managed sing-box with the version this awg-manager build is pinned to. No-op when versions match.
@Tags			singbox
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxStatusResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/update [post]

func (*SingboxHandler) UpdateTunnel

func (h *SingboxHandler) UpdateTunnel(w http.ResponseWriter, r *http.Request)

UpdateTunnel handles PUT /api/singbox/tunnels?tag={tag}. Body: {"outbound": {...}}.

@Summary		Update sing-box tunnel
@Tags			singbox
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/tunnels [put]

type SingboxPresenceProbe

type SingboxPresenceProbe interface {
	IsPresent() bool
}

SingboxPresenceProbe reports whether the managed sing-box binary is available on disk. Subscriptions register an NDMS Proxy interface pointing at sing-box's inbound listener, so creating one without sing-box installed leaves the Proxy slot pointing at nothing.

type SingboxProxiesHandler

type SingboxProxiesHandler struct {
	// contains filtered or unexported fields
}

SingboxProxiesHandler exposes runtime controls for sing-box composite outbounds by relaying typed requests to the upstream Clash API.

Dependencies are injected as functions so tests can swap them for httptest fakes:

  • clashBaseURL → returns the URL prefix to call (e.g. "http://127.0.0.1:9090") — same target the existing ClashProxy uses.
  • knownComposites → returns the set of composite tags we own (computed from 20-router.json). The List response is filtered to this set so Clash builtins (DIRECT, GLOBAL, etc.) and member outbounds don't leak into the UI.

func NewSingboxProxiesHandler

func NewSingboxProxiesHandler(clashBaseURL func() string, knownComposites func() map[string]struct{}, httpClient *http.Client) *SingboxProxiesHandler

NewSingboxProxiesHandler constructs the handler. httpClient may be nil; in production callers pass an http.Client tuned for the local loopback (short timeout, no keepalive).

func (*SingboxProxiesHandler) List

List godoc

@Summary		List sing-box composite proxy groups with live state
@Description	Returns selector/urltest/loadbalance groups managed by this router with their currently active member and per-member last latency. Filtered to groups defined in 20-router.json — Clash builtins (DIRECT, GLOBAL, REJECT) are excluded.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	OkResponse{data=SingboxProxiesListResponse}
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/proxies/list [get]

func (*SingboxProxiesHandler) Select

Select godoc

@Summary		Switch active member of a sing-box selector group
@Description	Sets the active member of a Clash-managed `selector` outbound. URLTest / loadbalance groups are read-only and return 400.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxProxiesSelectRequest	true	"Group and member tags"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Failure		502		{object}	APIErrorEnvelope
@Router			/singbox/router/proxies/select [post]

func (*SingboxProxiesHandler) Test

Test godoc

@Summary		Force latency test for all members of a composite group
@Description	Calls Clash `/group/<name>/delay`, returning the per-member delay map. Members that timed out come back with 0.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxProxiesTestRequest	true	"Group and optional URL/timeout"
@Success		200		{object}	OkResponse{data=SingboxProxiesTestResponse}
@Failure		400		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Failure		502		{object}	APIErrorEnvelope
@Router			/singbox/router/proxies/test [post]

type SingboxProxiesListResponse

type SingboxProxiesListResponse struct {
	Groups []SingboxProxyGroup `json:"groups"`
}

SingboxProxiesListResponse is the envelope payload for GET /list.

type SingboxProxiesSelectRequest

type SingboxProxiesSelectRequest struct {
	Group  string `json:"group"  example:"veesp-fast"`
	Member string `json:"member" example:"vless-1"`
}

SingboxProxiesSelectRequest is the body for POST /select.

type SingboxProxiesTestRequest

type SingboxProxiesTestRequest struct {
	Group   string `json:"group"`
	URL     string `json:"url,omitempty"`
	Timeout int    `json:"timeout,omitempty"`
}

SingboxProxiesTestRequest is the body for POST /test.

type SingboxProxiesTestResponse

type SingboxProxiesTestResponse struct {
	Delays map[string]int `json:"delays"`
}

SingboxProxiesTestResponse — memberTag → delay (ms); 0 = unreachable.

type SingboxProxyGroup

type SingboxProxyGroup struct {
	Tag     string               `json:"tag"`
	Type    string               `json:"type"`
	Now     string               `json:"now"`
	Members []SingboxProxyMember `json:"members"`
}

SingboxProxyGroup is one composite outbound (selector / urltest / loadbalance) with its current state.

type SingboxProxyMember

type SingboxProxyMember struct {
	Tag       string `json:"tag"`
	Type      string `json:"type"`
	LastDelay int    `json:"lastDelay,omitempty"`
}

SingboxProxyMember is one member outbound surfaced to the UI for a composite group. LastDelay is sourced from Clash's history[] tail; 0 means "no test recorded" or "last test failed" — UI treats both the same.

type SingboxRouterApplyPresetRequest

type SingboxRouterApplyPresetRequest struct {
	ID       string `json:"id" example:"china-direct"`
	Outbound string `json:"outbound" example:"awg-vpn0"`
}

SingboxRouterApplyPresetRequest is the body for POST /singbox/router/presets/apply.

type SingboxRouterBindDeviceRequest

type SingboxRouterBindDeviceRequest struct {
	MAC        string `json:"mac" example:"aa:bb:cc:dd:ee:ff"`
	PolicyName string `json:"policyName" example:"Policy0"`
}

SingboxRouterBindDeviceRequest is the body for POST /singbox/router/policy-devices/bind.

type SingboxRouterCreatePolicyRequest

type SingboxRouterCreatePolicyRequest struct {
	Description string `json:"description" example:"My VPN policy"`
}

SingboxRouterCreatePolicyRequest is the body for POST /singbox/router/policies.

type SingboxRouterDatRuleSetURLData

type SingboxRouterDatRuleSetURLData struct {
	URL string `json:"url" example:"http://127.0.0.1:2222/api/singbox/router/rulesets/dat-srs?kind=geosite&tag=GOOGLE&token=..."`
}

SingboxRouterDatRuleSetURLData is the payload for GET /singbox/router/rulesets/dat-url.

type SingboxRouterDatRuleSetURLResponse

type SingboxRouterDatRuleSetURLResponse struct {
	Success bool                           `json:"success" example:"true"`
	Data    SingboxRouterDatRuleSetURLData `json:"data"`
}

SingboxRouterDatRuleSetURLResponse is the envelope for dat rule-set URL metadata.

type SingboxRouterHandler

type SingboxRouterHandler struct {
	// contains filtered or unexported fields
}

func NewSingboxRouterHandler

func NewSingboxRouterHandler(svc router.Service, appLogger logging.AppLogger) *SingboxRouterHandler

func (*SingboxRouterHandler) AddDNSRule

func (h *SingboxRouterHandler) AddDNSRule(w http.ResponseWriter, r *http.Request)

AddDNSRule appends a new DNS routing rule.

@Summary		Add singbox-router DNS rule
@Description	Appends a new DNS routing rule. The rule's server tag must already exist.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRuleDTO	true	"DNS routing rule"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rules/add [post]

func (*SingboxRouterHandler) AddDNSServer

func (h *SingboxRouterHandler) AddDNSServer(w http.ResponseWriter, r *http.Request)

AddDNSServer registers a new DNS upstream.

@Summary		Add singbox-router DNS server
@Description	Registers a new DNS upstream (tag must be unique).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSServerDTO	true	"DNS server descriptor"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/servers/add [post]

func (*SingboxRouterHandler) AddOutbound

func (h *SingboxRouterHandler) AddOutbound(w http.ResponseWriter, r *http.Request)

AddOutbound creates a new composite outbound.

@Summary		Add singbox-router outbound
@Description	Creates a new composite outbound. The base outbounds it references must already exist.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterOutboundDTO	true	"Composite outbound payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/outbounds/add [post]

func (*SingboxRouterHandler) AddRule

AddRule appends a new singbox-router routing rule.

@Summary		Add singbox-router rule
@Description	Appends a new routing rule. Rule conditions reference rulesets/outbounds that must already exist.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleDTO	true	"Routing rule payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/rules/add [post]

func (*SingboxRouterHandler) AddRuleSet

func (h *SingboxRouterHandler) AddRuleSet(w http.ResponseWriter, r *http.Request)

AddRuleSet registers a new ruleset (downloads if remote).

@Summary		Add singbox-router ruleset
@Description	Registers a new ruleset. For remote rulesets the file is downloaded synchronously.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleSetDTO	true	"RuleSet payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/rulesets/add [post]

func (*SingboxRouterHandler) ApplyPreset

func (h *SingboxRouterHandler) ApplyPreset(w http.ResponseWriter, r *http.Request)

ApplyPreset materialises the named preset against the chosen outbound.

@Summary		Apply singbox-router preset
@Description	Materialises the preset (id) into rules + rulesets, routing matched traffic via the selected outbound. Existing rules with the same tag are overwritten.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterApplyPresetRequest	true	"Preset id + target outbound"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/presets/apply [post]

func (*SingboxRouterHandler) BindDevice

func (h *SingboxRouterHandler) BindDevice(w http.ResponseWriter, r *http.Request)

BindDevice handles POST /api/singbox/router/policy-devices/bind

@Summary		Bind device to singbox-router policy
@Description	Binds the LAN device (MAC) to the named policy. Replaces any existing binding.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterBindDeviceRequest	true	"Device MAC + target policy name"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/policy-devices/bind [post]

func (*SingboxRouterHandler) DatRuleSetSRS

func (h *SingboxRouterHandler) DatRuleSetSRS(w http.ResponseWriter, r *http.Request)

DatRuleSetSRS serves a compiled .srs artifact for sing-box. It is intentionally not protected by session cookies because sing-box fetches it as a plain remote rule_set URL; access is controlled by the token in the URL.

func (*SingboxRouterHandler) DatRuleSetURL

func (h *SingboxRouterHandler) DatRuleSetURL(w http.ResponseWriter, r *http.Request)

DatRuleSetURL returns the local tokenized URL that sing-box can fetch directly.

@Summary		Build dat→SRS rule-set URL
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Param			kind	query	string	true	"geosite or geoip"
@Param			tag		query	[]string	true	"Geo tag(s)"
@Success		200	{object}	SingboxRouterDatRuleSetURLResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/rulesets/dat-url [get]

func (*SingboxRouterHandler) DeleteDNSRule

func (h *SingboxRouterHandler) DeleteDNSRule(w http.ResponseWriter, r *http.Request)

DeleteDNSRule removes the DNS rule at the given index.

@Summary		Delete singbox-router DNS rule
@Description	Removes the DNS rule at the given index (0-based priority slot).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRuleDeleteRequest	true	"Index of the rule to remove"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rules/delete [post]

func (*SingboxRouterHandler) DeleteDNSServer

func (h *SingboxRouterHandler) DeleteDNSServer(w http.ResponseWriter, r *http.Request)

DeleteDNSServer removes the DNS upstream identified by tag.

@Summary		Delete singbox-router DNS server
@Description	Removes the DNS upstream identified by tag. Refuses if any DNS rule references it; pass force=true to override.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSServerDeleteRequest	true	"Tag + optional force flag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		409		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/servers/delete [post]

func (*SingboxRouterHandler) DeleteOutbound

func (h *SingboxRouterHandler) DeleteOutbound(w http.ResponseWriter, r *http.Request)

DeleteOutbound removes the composite outbound identified by tag.

@Summary		Delete singbox-router outbound
@Description	Removes the composite outbound identified by tag. Refuses if any rule references it; pass force=true to override.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterOutboundDeleteRequest	true	"Tag + optional force flag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		409		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/outbounds/delete [post]

func (*SingboxRouterHandler) DeleteRule

func (h *SingboxRouterHandler) DeleteRule(w http.ResponseWriter, r *http.Request)

DeleteRule removes the rule at the given index.

@Summary		Delete singbox-router rule
@Description	Removes the rule at the given index (0-based priority slot).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleDeleteRequest	true	"Index of the rule to remove"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/rules/delete [post]

func (*SingboxRouterHandler) DeleteRuleSet

func (h *SingboxRouterHandler) DeleteRuleSet(w http.ResponseWriter, r *http.Request)

DeleteRuleSet removes the ruleset identified by tag.

@Summary		Delete singbox-router ruleset
@Description	Removes the ruleset identified by tag. Refuses if any rule references it; pass force=true to remove this rule_set tag from referencing route and DNS rules.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleSetDeleteRequest	true	"Tag + optional force flag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		409		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/rulesets/delete [post]

func (*SingboxRouterHandler) Disable

Disable stops the singbox-router engine and uninstalls iptables/policy rules.

@Summary		Disable singbox-router
@Description	Stops the singbox-router engine and uninstalls iptables/policy rules. Idempotent.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	OkResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/disable [post]

func (*SingboxRouterHandler) Enable

Enable starts the singbox-router engine and installs iptables/policy rules.

@Summary		Enable singbox-router
@Description	Starts the singbox-router engine and installs iptables/policy rules. Returns 400 with code POLICY_NOT_CONFIGURED or POLICY_MISSING when the router policy mode is incomplete. Returns 503 SINGBOX_NOT_READY when sing-box did not become ready within the boot-wait window — iptables install is deliberately skipped to avoid orphaning DNS:53 redirects (issue #221).
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	OkResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Failure		503	{object}	APIErrorEnvelope	"sing-box did not come up in time"
@Router			/singbox/router/enable [post]

func (*SingboxRouterHandler) GetDNSGlobals

func (h *SingboxRouterHandler) GetDNSGlobals(w http.ResponseWriter, r *http.Request)

GetDNSGlobals returns the global DNS final/strategy fields.

@Summary		Get singbox-router DNS globals
@Description	Returns the global DNS settings: `final` (default server tag) and `strategy` (ipv4_only / prefer_ipv4 / etc.).
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxDNSGlobalsResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/dns/globals [get]

func (*SingboxRouterHandler) GetSettings

func (h *SingboxRouterHandler) GetSettings(w http.ResponseWriter, r *http.Request)

GetSettings reads singbox-router settings (policy-mode, defaults, etc.).

@Summary		Get singbox-router settings
@Description	Reads the current singbox-router settings (policy mode, defaults, ...).
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterSettingsResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/settings [get]

func (*SingboxRouterHandler) GetStaging

func (h *SingboxRouterHandler) GetStaging(w http.ResponseWriter, r *http.Request)

GetStaging returns the current draft state for the router slot.

@Summary		Get router staging status
@Description	Returns whether a pending draft exists for the router slot and, if so, its draft timestamp and a preview of the cross-slot validation result.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	RouterStagingStatusResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/staging [get]

func (*SingboxRouterHandler) GetStatus

func (h *SingboxRouterHandler) GetStatus(w http.ResponseWriter, r *http.Request)

GetStatus returns the current sing-box router engine status.

@Summary		Get sing-box router status
@Description	Returns the singbox-router status snapshot (running, mode, policy/iptables state, rule/ruleset/outbound counts).
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterStatusResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/status [get]

func (*SingboxRouterHandler) Inspect

Inspect simulates which router rule would match the given domain/IP.

@Summary		Inspect router routing decision
@Description	Simulates which router rule would match the given domain or IP, returning the would-be outbound. Matchers are evaluated in Go; rule_set matchers are additionally checked via sing-box rule-set match when the binary and rule-set files are available. If unavailable, rule_set degrades to no-match with an explanatory note.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterInspectRequest	true	"Domain or IP to test, plus optional port/protocol"
@Success		200		{object}	SingboxRouterInspectResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/inspect [post]

func (*SingboxRouterHandler) InspectDNS

func (h *SingboxRouterHandler) InspectDNS(w http.ResponseWriter, r *http.Request)

InspectDNS simulates which DNS rule would match the given domain and how the resolved DNS server classifies the resolution (fakeip/real/local).

@Summary		Inspect DNS-resolution decision
@Description	Simulates the DNS-resolution branch of the inspector: which dns.rule matches the domain, which DNS server answers, and whether the domain gets a fake-ip from a pool (→ tunnel), a real upstream IP, or a local (router) resolution. Mirrors the route inspector but over dns.rules. rule_set matchers are checked via sing-box rule-set match when available.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterInspectDNSRequest	true	"Domain to test, plus optional query type and source IP"
@Success		200		{object}	SingboxRouterInspectDNSResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/inspect-dns [post]

func (*SingboxRouterHandler) InspectStream

func (h *SingboxRouterHandler) InspectStream(w http.ResponseWriter, r *http.Request)

InspectStream streams route-inspector progress events via SSE.

Event names:

  • progress
  • result
  • inspect-error

Each SSE event `data` is a JSON object matching SingboxRouterInspectStreamEventDTO.

@Summary		Inspect router routing decision (SSE stream)
@Description	Streams route-inspector progress via Server-Sent Events. Event names: progress, result, inspect-error. Data payload for each event is JSON shaped as SingboxRouterInspectStreamEventDTO.
@Tags			singbox-router
@Produce		text/event-stream
@Security		CookieAuth
@Param			domain		query		string	true	"Domain or IP to test"
@Param			port		query		int		false	"Destination port, 0-65535"
@Param			protocol	query		string	false	"Protocol (tcp/udp)"
@Success		200			{object}	SingboxRouterInspectStreamEventDTO	"SSE event data payload"
@Failure		400			{object}	APIErrorEnvelope
@Failure		500			{object}	APIErrorEnvelope
@Router			/singbox/router/inspect/stream [get]

func (*SingboxRouterHandler) ListBindableInterfaces

func (h *SingboxRouterHandler) ListBindableInterfaces(w http.ResponseWriter, r *http.Request)

ListBindableInterfaces returns interfaces a user can bind a direct outbound to.

@Summary		List bindable interfaces for direct outbounds
@Description	Returns router interfaces (minus our own and AWG/WG auto-covered) that a direct outbound can bind to. Fields id and priority are not populated for this endpoint (only name, label, up are meaningful).
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterWANInterfacesListResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/bindable-interfaces [get]

func (*SingboxRouterHandler) ListDNSRules

func (h *SingboxRouterHandler) ListDNSRules(w http.ResponseWriter, r *http.Request)

ListDNSRules returns all DNS routing rules in priority order.

@Summary		List singbox-router DNS rules
@Description	Returns all DNS routing rules in priority (top-first) order. Always a JSON array, never null.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxDNSRulesListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rules/list [get]

func (*SingboxRouterHandler) ListDNSServers

func (h *SingboxRouterHandler) ListDNSServers(w http.ResponseWriter, r *http.Request)

ListDNSServers returns all configured DNS servers.

@Summary		List singbox-router DNS servers
@Description	Returns all configured DNS upstreams (tag, address, type, ...). Always a JSON array, never null.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxDNSServersListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/dns/servers/list [get]

func (*SingboxRouterHandler) ListIngressEligibleInterfaces

func (h *SingboxRouterHandler) ListIngressEligibleInterfaces(w http.ResponseWriter, r *http.Request)

ListIngressEligibleInterfaces returns interfaces eligible for sing-box ingress-scope.

@Summary		List ingress-eligible interfaces
@Description	Returns router interfaces eligible for sing-box ingress-scope (bindable minus WAN minus LAN bridges). Used by the ingress multiselect in singbox-router settings.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterWANInterfacesListResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/ingress-eligible-interfaces [get]

func (*SingboxRouterHandler) ListOutbounds

func (h *SingboxRouterHandler) ListOutbounds(w http.ResponseWriter, r *http.Request)

ListOutbounds returns all composite outbounds.

@Summary		List singbox-router outbounds
@Description	Returns all composite outbounds (sing-box selectors/urltests over multiple base outbounds).
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterOutboundsListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/outbounds/list [get]

func (*SingboxRouterHandler) ListPolicyDevices

func (h *SingboxRouterHandler) ListPolicyDevices(w http.ResponseWriter, r *http.Request)

ListPolicyDevices handles GET /api/singbox/router/policy-devices?name=X

@Summary		List singbox-router policy devices
@Description	Returns the LAN devices currently bound to the named policy. Always a JSON array, never null.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Param			name	query		string	true	"Policy name"
@Success		200		{object}	SingboxRouterPolicyDevicesListResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/policy-devices [get]

func (*SingboxRouterHandler) ListPresets

func (h *SingboxRouterHandler) ListPresets(w http.ResponseWriter, r *http.Request)

ListPresets returns the catalog of built-in singbox-router presets.

@Summary		List singbox-router presets
@Description	Returns the catalog of built-in presets the user can apply (each preset = a curated bundle of rules + rulesets).
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterPresetsListResponse
@Failure		405	{object}	APIErrorEnvelope
@Router			/singbox/router/presets/list [get]

func (*SingboxRouterHandler) ListRuleSets

func (h *SingboxRouterHandler) ListRuleSets(w http.ResponseWriter, r *http.Request)

ListRuleSets returns all configured rulesets.

@Summary		List singbox-router rulesets
@Description	Returns all configured rulesets (downloaded geo files / inline lists), with their tag, type, and freshness metadata.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterRuleSetsListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/rulesets/list [get]

func (*SingboxRouterHandler) ListRules

func (h *SingboxRouterHandler) ListRules(w http.ResponseWriter, r *http.Request)

ListRules returns all singbox-router routing rules in priority order.

@Summary		List singbox-router rules
@Description	Returns all routing rules in priority (top-first) order.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterRulesListResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/rules/list [get]

func (*SingboxRouterHandler) ListWANInterfaces

func (h *SingboxRouterHandler) ListWANInterfaces(w http.ResponseWriter, r *http.Request)

ListWANInterfaces returns all router WAN interfaces for the WAN-binding picker. No up/down filtering — the UI shows every interface and the user picks.

@Summary		List WAN interfaces
@Description	Returns all router WAN interfaces (no up/down filtering) used by the WAN-binding picker in singbox-router settings. Always a JSON array, never null. The `name` field is the kernel system-name and is the value that should be persisted into `wanInterface`.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SingboxRouterWANInterfacesListResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/wan-interfaces [get]

func (*SingboxRouterHandler) MoveDNSRule

func (h *SingboxRouterHandler) MoveDNSRule(w http.ResponseWriter, r *http.Request)

MoveDNSRule moves a DNS rule from one priority slot to another.

@Summary		Move singbox-router DNS rule
@Description	Moves the DNS rule from index `from` to index `to` (both 0-based).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRuleMoveRequest	true	"From-index and to-index"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rules/move [post]

func (*SingboxRouterHandler) MoveDNSServer

func (h *SingboxRouterHandler) MoveDNSServer(w http.ResponseWriter, r *http.Request)

MoveDNSServer reorders a DNS server from one slot to another.

@Summary		Move singbox-router DNS server
@Description	Moves the DNS server from index `from` to index `to` (both 0-based). Server order is cosmetic — sing-box references servers by tag; this exists only for UX-consistent reordering.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSServerMoveRequest	true	"From-index and to-index"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/servers/move [post]

func (*SingboxRouterHandler) MoveRule

MoveRule moves the rule from one priority slot to another.

@Summary		Move singbox-router rule
@Description	Moves the rule from index `from` to index `to` (both 0-based). Adjusts other rules' indices accordingly.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleMoveRequest	true	"From-index and to-index"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/rules/move [post]

func (*SingboxRouterHandler) PoliciesCollection

func (h *SingboxRouterHandler) PoliciesCollection(w http.ResponseWriter, r *http.Request)

PoliciesCollection routes by HTTP method:

GET  → ListPolicies (returns []router.PolicyInfo)
POST → CreatePolicy (body: {description}, returns router.PolicyInfo)

func (*SingboxRouterHandler) PostStagingApply

func (h *SingboxRouterHandler) PostStagingApply(w http.ResponseWriter, r *http.Request)

PostStagingApply commits the pending draft.

@Summary		Apply router staging draft
@Description	Validates the pending draft (cross-slot + sing-box check) then atomically swaps pending → active and arms a reload.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	OkResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		409	{object}	APIErrorEnvelope	"no draft to apply"
@Failure		422	{object}	RouterStagingValidationError
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/staging/apply [post]

func (*SingboxRouterHandler) PostStagingDiscard

func (h *SingboxRouterHandler) PostStagingDiscard(w http.ResponseWriter, r *http.Request)

PostStagingDiscard removes the pending draft.

@Summary		Discard router staging draft
@Description	Removes pending/20-router.json. Idempotent.
@Tags			singbox-router
@Produce		json
@Security		CookieAuth
@Success		200	{object}	OkResponse
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/router/staging/discard [post]

func (*SingboxRouterHandler) PutDNSGlobals

func (h *SingboxRouterHandler) PutDNSGlobals(w http.ResponseWriter, r *http.Request)

PutDNSGlobals persists global DNS final/strategy fields.

@Summary		Update singbox-router DNS globals
@Description	Persists the global DNS settings: `final` (default server tag) and `strategy` (ipv4_only / prefer_ipv4 / etc.).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSGlobalsData	true	"final + strategy"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/globals [post]
@Router			/singbox/router/dns/globals [put]

func (*SingboxRouterHandler) PutSettings

func (h *SingboxRouterHandler) PutSettings(w http.ResponseWriter, r *http.Request)

PutSettings persists singbox-router settings.

@Summary		Update singbox-router settings
@Description	Persists singbox-router settings. The router is restarted only when fields that affect the running config change.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterSettingsData	true	"Singbox-router settings payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/settings [post]
@Router			/singbox/router/settings [put]

func (*SingboxRouterHandler) SetOutboundRefCheckers

SetOutboundRefCheckers wires device-proxy and router reference guards for composite-outbound deletion (refuse 409 when the tag is still selected by a device-proxy instance).

func (*SingboxRouterHandler) SetRouteFinal

func (h *SingboxRouterHandler) SetRouteFinal(w http.ResponseWriter, r *http.Request)

SetRouteFinal updates route.final.

@Summary		Set route.final outbound
@Description	Updates the route.final fallback outbound. Use "direct" for default sing-box direct, or the tag of any existing outbound (composite, AWG, sing-box tunnel).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRouteFinalRequest	true	"New final outbound tag"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/route/final [post]

func (*SingboxRouterHandler) SwitchMode

func (h *SingboxRouterHandler) SwitchMode(w http.ResponseWriter, r *http.Request)

SwitchMode orchestrates a routing-mode transition (off↔tproxy↔fakeip-tun) with directional fail-closed rollback. Progress is reported out-of-band as "singbox-router:transition" events on the existing events SSE stream (GET /events) — no new stream endpoint.

@Summary		Switch singbox-router routing mode
@Description	Orchestrates a routing-mode transition (off↔tproxy↔fakeip-tun): tears down the old mode then brings up the new one, with directional fail-closed rollback. Per-step progress is published as "singbox-router:transition" events on the existing GET /events SSE stream (see SingboxRouterTransitionData). Returns 400 INVALID_MODE for an unknown mode.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterModeRequest	true	"Target routing mode"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		405		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/mode [post]

func (*SingboxRouterHandler) UnbindDevice

func (h *SingboxRouterHandler) UnbindDevice(w http.ResponseWriter, r *http.Request)

UnbindDevice handles POST /api/singbox/router/policy-devices/unbind

@Summary		Unbind device from singbox-router policy
@Description	Removes any policy binding for the LAN device identified by MAC.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterUnbindDeviceRequest	true	"Device MAC"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/policy-devices/unbind [post]

func (*SingboxRouterHandler) UpdateDNSRule

func (h *SingboxRouterHandler) UpdateDNSRule(w http.ResponseWriter, r *http.Request)

UpdateDNSRule replaces the DNS rule at the given index.

@Summary		Update singbox-router DNS rule
@Description	Replaces the DNS rule at the given index (0-based priority slot) with the provided one.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSRuleUpdateRequest	true	"Index + replacement rule"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/rules/update [post]

func (*SingboxRouterHandler) UpdateDNSServer

func (h *SingboxRouterHandler) UpdateDNSServer(w http.ResponseWriter, r *http.Request)

UpdateDNSServer replaces the DNS upstream identified by tag.

@Summary		Update singbox-router DNS server
@Description	Replaces the DNS upstream identified by tag with the provided one.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxDNSServerUpdateRequest	true	"Tag + replacement server"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/dns/servers/update [post]

func (*SingboxRouterHandler) UpdateOutbound

func (h *SingboxRouterHandler) UpdateOutbound(w http.ResponseWriter, r *http.Request)

UpdateOutbound replaces the composite outbound identified by tag.

@Summary		Update singbox-router outbound
@Description	Replaces the composite outbound identified by tag with the provided one.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterOutboundUpdateRequest	true	"Tag + replacement outbound"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/outbounds/update [post]

func (*SingboxRouterHandler) UpdateRule

func (h *SingboxRouterHandler) UpdateRule(w http.ResponseWriter, r *http.Request)

UpdateRule replaces a rule at the given index with the provided one.

@Summary		Update singbox-router rule
@Description	Replaces the rule at index with the provided one. Index is the priority slot (0-based).
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleUpdateRequest	true	"Index + replacement rule"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/rules/update [post]

func (*SingboxRouterHandler) UpdateRuleSet

func (h *SingboxRouterHandler) UpdateRuleSet(w http.ResponseWriter, r *http.Request)

UpdateRuleSet replaces the ruleset identified by tag with new content.

@Summary		Update singbox-router ruleset
@Description	Replaces the ruleset identified by tag with new content. If the payload tag differs, references are renamed atomically.
@Tags			singbox-router
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		SingboxRouterRuleSetUpdateRequest	true	"Tag + new RuleSet payload"
@Success		200		{object}	OkResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/router/rulesets/update [post]

type SingboxRouterInspectDNSData

type SingboxRouterInspectDNSData struct {
	Input          string                            `json:"input" example:"discord.com"`
	InputType      string                            `json:"inputType" example:"domain"`
	Matches        []SingboxRouterInspectDNSMatchDTO `json:"matches"`
	MatchedRule    int                               `json:"matchedRule" example:"5"`
	Server         string                            `json:"server" example:"fakeip"`
	Classification string                            `json:"classification" example:"fakeip"`
	Pool           string                            `json:"pool,omitempty" example:"198.18.0.0/15"`
	Final          string                            `json:"final" example:"fakeip"`
	Note           string                            `json:"note,omitempty"`
}

SingboxRouterInspectDNSData mirrors router.InspectDNSResult.

type SingboxRouterInspectDNSMatchDTO

type SingboxRouterInspectDNSMatchDTO struct {
	Index      int      `json:"index" example:"0"`
	Matched    bool     `json:"matched" example:"true"`
	Server     string   `json:"server,omitempty" example:"fakeip"`
	Conditions []string `json:"conditions,omitempty"`
	Reason     string   `json:"reason,omitempty" example:"совпало по: query_type"`
}

SingboxRouterInspectDNSMatchDTO mirrors router.DNSRuleMatchResult.

type SingboxRouterInspectDNSRequest

type SingboxRouterInspectDNSRequest struct {
	Domain    string `json:"domain" example:"discord.com"`
	QueryType string `json:"queryType,omitempty" example:"A"`
	SourceIP  string `json:"sourceIP,omitempty" example:"192.168.1.70"`
}

SingboxRouterInspectDNSRequest is the body for POST /singbox/router/inspect-dns.

type SingboxRouterInspectDNSResponse

type SingboxRouterInspectDNSResponse struct {
	Success bool                        `json:"success" example:"true"`
	Data    SingboxRouterInspectDNSData `json:"data"`
}

SingboxRouterInspectDNSResponse is the envelope for POST /singbox/router/inspect-dns.

type SingboxRouterInspectData

type SingboxRouterInspectData struct {
	Input       string                         `json:"input" example:"google.com"`
	InputType   string                         `json:"inputType" example:"domain"`
	Matches     []SingboxRouterInspectMatchDTO `json:"matches"`
	Destination string                         `json:"destination" example:"vpn"`
	MatchedRule int                            `json:"matchedRule" example:"0"`
	Final       string                         `json:"final" example:"direct"`
	Note        string                         `json:"note,omitempty"`
}

SingboxRouterInspectData mirrors router.InspectResult.

type SingboxRouterInspectMatchDTO

type SingboxRouterInspectMatchDTO struct {
	Index      int      `json:"index" example:"0"`
	Matched    bool     `json:"matched" example:"true"`
	Action     string   `json:"action" example:"route"`
	Outbound   string   `json:"outbound,omitempty" example:"vpn"`
	Conditions []string `json:"conditions,omitempty"`
	Reason     string   `json:"reason,omitempty" example:"совпало по: domain_suffix"`
}

SingboxRouterInspectMatchDTO mirrors router.RuleMatchResult.

type SingboxRouterInspectProgressDTO

type SingboxRouterInspectProgressDTO struct {
	Phase        string `json:"phase" example:"rule_set_match_start"`
	Message      string `json:"message" example:"Проверяем rule_set geosite-youtube через sing-box…"`
	RuleIndex    *int   `json:"ruleIndex,omitempty" example:"3"`
	RuleTotal    *int   `json:"ruleTotal,omitempty" example:"17"`
	RuleSetTag   string `json:"ruleSetTag,omitempty" example:"geosite-youtube"`
	RuleSetIndex *int   `json:"ruleSetIndex,omitempty" example:"0"`
	RuleSetTotal *int   `json:"ruleSetTotal,omitempty" example:"2"`
	Final        string `json:"final,omitempty" example:"direct"`
	UsingDraft   bool   `json:"usingDraft,omitempty" example:"true"`
}

SingboxRouterInspectProgressDTO mirrors router.InspectProgress.

type SingboxRouterInspectRequest

type SingboxRouterInspectRequest struct {
	Domain   string `json:"domain" example:"google.com"`
	Port     int    `json:"port,omitempty" example:"443"`
	Protocol string `json:"protocol,omitempty" example:"tcp"`
}

SingboxRouterInspectRequest is the body for POST /singbox/router/inspect.

type SingboxRouterInspectResponse

type SingboxRouterInspectResponse struct {
	Success bool                     `json:"success" example:"true"`
	Data    SingboxRouterInspectData `json:"data"`
}

SingboxRouterInspectResponse is the envelope for POST /singbox/router/inspect.

type SingboxRouterInspectStreamEventDTO

type SingboxRouterInspectStreamEventDTO struct {
	Type     string                           `json:"type" example:"progress"`
	Progress *SingboxRouterInspectProgressDTO `json:"progress,omitempty"`
	Result   *SingboxRouterInspectData        `json:"result,omitempty"`
	Error    string                           `json:"error,omitempty" example:"load router config: ..."`
}

SingboxRouterInspectStreamEventDTO mirrors router.InspectStreamEvent.

type SingboxRouterIssueDTO

type SingboxRouterIssueDTO struct {
	Severity  string `json:"severity" example:"warning"`
	Kind      string `json:"kind" example:"missing-outbound"`
	RuleIndex int    `json:"ruleIndex,omitempty" example:"0"`
	Tag       string `json:"tag,omitempty" example:"selector"`
	Message   string `json:"message" example:"outbound 'selector' is referenced but does not exist"`
}

SingboxRouterIssueDTO mirrors router.Issue (one entry of Status.Issues).

type SingboxRouterModeRequest

type SingboxRouterModeRequest struct {
	Mode string `json:"mode" example:"fakeip-tun" enums:"off,tproxy,fakeip-tun"`
}

SingboxRouterModeRequest is the body for POST /singbox/router/mode.

type SingboxRouterOutboundDTO

type SingboxRouterOutboundDTO struct {
	Type          string   `json:"type" example:"selector"`
	Tag           string   `json:"tag" example:"my-selector"`
	BindInterface string   `json:"bind_interface,omitempty" example:"awg-vpn0"`
	Outbounds     []string `json:"outbounds,omitempty" example:"awg-vpn0"`
	URL           string   `json:"url,omitempty" example:"https://www.gstatic.com/generate_204"`
	Interval      string   `json:"interval,omitempty" example:"3m"`
	Tolerance     int      `json:"tolerance,omitempty" example:"50"`
	Default       string   `json:"default,omitempty" example:"awg-vpn0"`
	Strategy      string   `json:"strategy,omitempty" example:"prefer_ipv4"`
	Source        string   `json:"source" example:"router" enums:"router,subscription"`
}

SingboxRouterOutboundDTO mirrors router.Outbound (composite outbound).

type SingboxRouterOutboundDeleteRequest

type SingboxRouterOutboundDeleteRequest struct {
	Tag   string `json:"tag" example:"my-selector"`
	Force bool   `json:"force" example:"false"`
}

SingboxRouterOutboundDeleteRequest is the body for POST /singbox/router/outbounds/delete.

type SingboxRouterOutboundUpdateRequest

type SingboxRouterOutboundUpdateRequest struct {
	Tag      string                   `json:"tag" example:"my-selector"`
	Outbound SingboxRouterOutboundDTO `json:"outbound"`
}

SingboxRouterOutboundUpdateRequest is the body for POST /singbox/router/outbounds/update.

type SingboxRouterOutboundsListResponse

type SingboxRouterOutboundsListResponse struct {
	Success bool                       `json:"success" example:"true"`
	Data    []SingboxRouterOutboundDTO `json:"data"`
}

SingboxRouterOutboundsListResponse is the envelope for GET /singbox/router/outbounds/list.

type SingboxRouterPoliciesListResponse

type SingboxRouterPoliciesListResponse struct {
	Success bool                         `json:"success" example:"true"`
	Data    []SingboxRouterPolicyInfoDTO `json:"data"`
}

SingboxRouterPoliciesListResponse is the envelope for GET /singbox/router/policies.

type SingboxRouterPolicyDeviceDTO

type SingboxRouterPolicyDeviceDTO struct {
	MAC   string `json:"mac" example:"aa:bb:cc:dd:ee:ff"`
	IP    string `json:"ip" example:"192.168.1.100"`
	Name  string `json:"name,omitempty" example:"My Phone"`
	Bound bool   `json:"bound" example:"true"`
}

SingboxRouterPolicyDeviceDTO mirrors router.PolicyDevice.

type SingboxRouterPolicyDevicesListResponse

type SingboxRouterPolicyDevicesListResponse struct {
	Success bool                           `json:"success" example:"true"`
	Data    []SingboxRouterPolicyDeviceDTO `json:"data"`
}

SingboxRouterPolicyDevicesListResponse is the envelope for GET /singbox/router/policy-devices.

type SingboxRouterPolicyInfoDTO

type SingboxRouterPolicyInfoDTO struct {
	Name         string `json:"name" example:"Policy0"`
	Description  string `json:"description" example:"Default policy"`
	Mark         string `json:"mark,omitempty" example:"0xffffaaa"`
	DeviceCount  int    `json:"deviceCount" example:"3"`
	IsOurDefault bool   `json:"isOurDefault" example:"false"`
}

SingboxRouterPolicyInfoDTO mirrors router.PolicyInfo (NDMS policy projection).

type SingboxRouterPolicyResponse

type SingboxRouterPolicyResponse struct {
	Success bool                       `json:"success" example:"true"`
	Data    SingboxRouterPolicyInfoDTO `json:"data"`
}

SingboxRouterPolicyResponse is the envelope for POST /singbox/router/policies (single policy).

type SingboxRouterPresetDTO

type SingboxRouterPresetDTO struct {
	ID        string                           `json:"id" example:"china-direct"`
	Name      string                           `json:"name" example:"China Direct"`
	Category  string                           `json:"category,omitempty" example:"social"`
	IconSlug  string                           `json:"iconSlug,omitempty" example:"china"`
	RuleSets  []SingboxRouterPresetRuleRefDTO  `json:"ruleSets"`
	Rules     []SingboxRouterPresetRuleLinkDTO `json:"rules"`
	Notice    string                           `json:"notice,omitempty" example:"Routes mainland China traffic via the direct outbound."`
	Covers    []string                         `json:"covers,omitempty" example:"instagram,whatsapp"`
	Featured  bool                             `json:"featured,omitempty" example:"true"`
	Sensitive bool                             `json:"sensitive,omitempty" example:"false"`
}

SingboxRouterPresetDTO mirrors router.Preset (one entry of the preset catalog).

type SingboxRouterPresetRuleLinkDTO

type SingboxRouterPresetRuleLinkDTO struct {
	RuleSet      []string `json:"rule_set,omitempty" example:"geosite-cn"`
	DomainSuffix []string `json:"domain_suffix,omitempty" example:".cn"`
	Action       string   `json:"action,omitempty" example:"route"`
}

SingboxRouterPresetRuleLinkDTO mirrors router.RuleLink.

type SingboxRouterPresetRuleRefDTO

type SingboxRouterPresetRuleRefDTO struct {
	Tag string `json:"tag" example:"geosite-cn"`
}

SingboxRouterPresetRuleRefDTO mirrors router.RuleRef.

type SingboxRouterPresetsListResponse

type SingboxRouterPresetsListResponse struct {
	Success bool                     `json:"success" example:"true"`
	Data    []SingboxRouterPresetDTO `json:"data"`
}

SingboxRouterPresetsListResponse is the envelope for GET /singbox/router/presets/list.

type SingboxRouterRouteFinalRequest

type SingboxRouterRouteFinalRequest struct {
	Final string `json:"final" example:"direct"`
}

SingboxRouterRouteFinalRequest is the body for POST /singbox/router/route/final.

type SingboxRouterRuleDTO

type SingboxRouterRuleDTO struct {
	DomainSuffix []string `json:"domain_suffix,omitempty" example:".example.com"`
	IPCIDR       []string `json:"ip_cidr,omitempty" example:"10.0.0.0/8"`
	SourceIPCIDR []string `json:"source_ip_cidr,omitempty" example:"192.168.1.100/32"`
	Port         []int    `json:"port,omitempty" example:"443"`
	RuleSet      []string `json:"rule_set,omitempty" example:"geosite-cn"`
	Protocol     string   `json:"protocol,omitempty" example:"tcp"`
	Action       string   `json:"action" example:"route"`
	Outbound     string   `json:"outbound,omitempty" example:"selector"`
}

SingboxRouterRuleDTO mirrors router.Rule (a routing rule in priority order).

type SingboxRouterRuleDeleteRequest

type SingboxRouterRuleDeleteRequest struct {
	Index int `json:"index" example:"0"`
}

SingboxRouterRuleDeleteRequest is the body for POST /singbox/router/rules/delete.

type SingboxRouterRuleMoveRequest

type SingboxRouterRuleMoveRequest struct {
	From int `json:"from" example:"3"`
	To   int `json:"to" example:"0"`
}

SingboxRouterRuleMoveRequest is the body for POST /singbox/router/rules/move.

type SingboxRouterRuleSetDTO

type SingboxRouterRuleSetDTO struct {
	Tag             string           `json:"tag" example:"geosite-cn"`
	Type            string           `json:"type" example:"remote"`
	Format          string           `json:"format,omitempty" example:"binary"`
	URL             string           `json:"url,omitempty" example:"https://cdn.example.com/geosite-cn.srs"`
	UpdateInterval  string           `json:"update_interval,omitempty" example:"24h"`
	DownloadDetour  string           `json:"download_detour,omitempty" example:"direct"`
	Path            string           `json:"path,omitempty" example:"/opt/etc/singbox/rulesets/geosite-cn.srs"`
	Rules           []map[string]any `json:"rules,omitempty"`
	MaterializedSRS bool             `json:"materialized_srs,omitempty" example:"true"`
}

SingboxRouterRuleSetDTO mirrors router.RuleSet.

type SingboxRouterRuleSetDeleteRequest

type SingboxRouterRuleSetDeleteRequest struct {
	Tag   string `json:"tag" example:"geosite-cn"`
	Force bool   `json:"force" example:"false"`
}

SingboxRouterRuleSetDeleteRequest is the body for POST /singbox/router/rulesets/delete.

type SingboxRouterRuleSetUpdateRequest

type SingboxRouterRuleSetUpdateRequest struct {
	Tag     string                  `json:"tag" example:"geosite-cn"`
	RuleSet SingboxRouterRuleSetDTO `json:"ruleSet"`
}

SingboxRouterRuleSetUpdateRequest is the body for POST /singbox/router/rulesets/update.

type SingboxRouterRuleSetsListResponse

type SingboxRouterRuleSetsListResponse struct {
	Success bool                      `json:"success" example:"true"`
	Data    []SingboxRouterRuleSetDTO `json:"data"`
}

SingboxRouterRuleSetsListResponse is the envelope for GET /singbox/router/rulesets/list.

type SingboxRouterRuleUpdateRequest

type SingboxRouterRuleUpdateRequest struct {
	Index int                  `json:"index" example:"0"`
	Rule  SingboxRouterRuleDTO `json:"rule"`
}

SingboxRouterRuleUpdateRequest is the body for POST /singbox/router/rules/update.

type SingboxRouterRulesListResponse

type SingboxRouterRulesListResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    []SingboxRouterRuleDTO `json:"data"`
}

SingboxRouterRulesListResponse is the envelope for GET /singbox/router/rules/list.

type SingboxRouterSettingsData

type SingboxRouterSettingsData struct {
	Enabled        bool   `json:"enabled" example:"true"`
	PolicyName     string `json:"policyName" example:"awgm-router"`
	DeviceMode     string `json:"deviceMode,omitempty" example:"policy" enums:"policy,all"`
	RoutingMode    string `json:"routingMode,omitempty" example:"tproxy" enums:"tproxy,fakeip-tun"`
	SnifferEnabled bool   `json:"snifferEnabled" example:"true"`
	// WANAutoDetect / WANInterface form a two-field discriminator:
	//   true  + ""    → sing-box auto_detect_interface
	//   false + "ppp0"→ sing-box default_interface=ppp0
	// Other combinations are rejected by the backend validator.
	// Example below shows the PINNED case as it's the more interesting
	// shape to document (auto case has WANInterface omitted via omitempty
	// and wanAutoDetect=true); both examples are intentionally consistent.
	WANAutoDetect bool   `json:"wanAutoDetect" example:"false"`
	WANInterface  string `json:"wanInterface,omitempty" example:"ppp0"`
	// BypassPresets lists active named port-bypass presets.
	// Valid values: "l2tp", "ntp", "netbios-smb".
	BypassPresets []string `json:"bypassPresets,omitempty" example:"l2tp"`
	// BypassExtraPorts is a user-defined comma-separated list of extra
	// port exclusions in "PORT UDP|TCP" format (e.g. "51820 UDP, 1194 TCP").
	BypassExtraPorts string `json:"bypassExtraPorts,omitempty" example:"51820 UDP"`
	// BypassExtraSubnets is a user-defined comma/space-separated list of IPv4
	// IP/CIDR destinations whose traffic bypasses sing-box entirely (incl.
	// DNS/53). Bare IP is treated as /32. E.g. "203.0.113.0/24, 10.8.0.5".
	BypassExtraSubnets string `json:"bypassExtraSubnets,omitempty" example:"203.0.113.0/24"`
	// IngressInterfaces lists interface refs whose ingress traffic is
	// redirected through the sing-box router (e.g. "managed:Wireguard3").
	IngressInterfaces []string `json:"ingressInterfaces,omitempty" example:"managed:Wireguard3"`
	// --- fakeip-tun engine settings (user-editable) ---
	// FakeIPStack selects the sing-tun stack: "gvisor" (default) or "system".
	FakeIPStack string `json:"fakeipStack,omitempty" example:"gvisor" enums:"gvisor,system"`
	// FakeIPPool4 is the fakeip v4 pool CIDR (default "198.18.0.0/15").
	FakeIPPool4 string `json:"fakeipPool4,omitempty" example:"198.18.0.0/15"`
	// FakeIPPool6 is the fakeip v6 pool CIDR (default "fc00::/18"); "" disables v6.
	FakeIPPool6 string `json:"fakeipPool6,omitempty" example:"fc00::/18"`
	// FakeIPMTU is the tun MTU (default 1500; valid range 576-9000).
	FakeIPMTU int `json:"fakeipMtu,omitempty" example:"1500"`
	// UDPTimeout sets the UDP session timeout for the tproxy-in inbound
	// (Go duration string, e.g. "3m0s", "10m0s"). Empty = use default (3m0s).
	// Increase to prevent long-quiet UDP applications (games, etc.) from
	// having their sessions silently dropped mid-game.
	UDPTimeout string `json:"udpTimeout,omitempty" example:"10m0s"`
}

SingboxRouterSettingsData mirrors storage.SingboxRouterSettings.

type SingboxRouterSettingsResponse

type SingboxRouterSettingsResponse struct {
	Success bool                      `json:"success" example:"true"`
	Data    SingboxRouterSettingsData `json:"data"`
}

SingboxRouterSettingsResponse is the envelope for GET /singbox/router/settings.

type SingboxRouterStatusData

type SingboxRouterStatusData struct {
	Enabled                bool                    `json:"enabled" example:"true"`
	Installed              bool                    `json:"installed" example:"true"`
	Active                 bool                    `json:"active" example:"true"`
	NetfilterAvailable     bool                    `json:"netfilterAvailable" example:"true"`
	NetfilterComponentName string                  `json:"netfilterComponentName,omitempty" example:"iptables-mod-tproxy"`
	TProxyTargetAvailable  bool                    `json:"tproxyTargetAvailable" example:"true"`
	PolicyName             string                  `json:"policyName" example:"awgm-router"`
	PolicyMark             string                  `json:"policyMark,omitempty" example:"0xffffaaa"`
	PolicyExists           bool                    `json:"policyExists" example:"true"`
	DeviceMode             string                  `json:"deviceMode" example:"policy" enums:"policy,all"`
	SnifferEnabled         bool                    `json:"snifferEnabled" example:"true"`
	DeviceCount            int                     `json:"deviceCount" example:"3"`
	RuleCount              int                     `json:"ruleCount" example:"12"`
	RuleSetCount           int                     `json:"ruleSetCount" example:"4"`
	OutboundAWGCount       int                     `json:"outboundAwgCount" example:"2"`
	OutboundCompositeCount int                     `json:"outboundCompositeCount" example:"1"`
	Final                  string                  `json:"final" example:"direct"`
	FakeIPIface            string                  `json:"fakeipIface,omitempty" example:"opkgtun0"`
	FakeIPDns              string                  `json:"fakeipDns,omitempty" example:"172.18.0.2"`
	FakeIPTunAddr          string                  `json:"fakeipTunAddr,omitempty" example:"172.18.0.1"`
	LastError              string                  `json:"lastError,omitempty" example:"engine start failed"`
	Issues                 []SingboxRouterIssueDTO `json:"issues,omitempty"`
}

SingboxRouterStatusData mirrors router.Status.

type SingboxRouterStatusResponse

type SingboxRouterStatusResponse struct {
	Success bool                    `json:"success" example:"true"`
	Data    SingboxRouterStatusData `json:"data"`
}

SingboxRouterStatusResponse is the envelope for GET /singbox/router/status.

type SingboxRouterTransitionData

type SingboxRouterTransitionData struct {
	TransitionID string                         `json:"transitionId" example:"switch-7"`
	From         string                         `json:"from" example:"tproxy" enums:"off,tproxy,fakeip-tun"`
	To           string                         `json:"to" example:"fakeip-tun" enums:"off,tproxy,fakeip-tun"`
	Step         SingboxRouterTransitionStepDTO `json:"step"`
	Done         bool                           `json:"done,omitempty" example:"true"`
	FinalState   string                         `json:"finalState,omitempty" example:"fakeip-tun" enums:"off,tproxy,fakeip-tun"`
	Error        string                         `json:"error,omitempty" example:""`
}

SingboxRouterTransitionData mirrors router.TransitionEvent. It documents the payload of the "singbox-router:transition" events emitted on the GET /events SSE stream during a POST /singbox/router/mode switch (the UI progress screen).

type SingboxRouterTransitionStepDTO

type SingboxRouterTransitionStepDTO struct {
	Step    string `json:"step" example:"provision" enums:"start,teardown,provision,readiness,ready,rollback,error"`
	Status  string `json:"status" example:"current" enums:"current,done,error"`
	Message string `json:"message,omitempty" example:"restored tproxy"`
}

SingboxRouterTransitionStepDTO mirrors router.TransitionStep — one milestone of a routing-mode switch.

type SingboxRouterUnbindDeviceRequest

type SingboxRouterUnbindDeviceRequest struct {
	MAC string `json:"mac" example:"aa:bb:cc:dd:ee:ff"`
}

SingboxRouterUnbindDeviceRequest is the body for POST /singbox/router/policy-devices/unbind.

type SingboxRouterWANInterfaceDTO

type SingboxRouterWANInterfaceDTO struct {
	Name     string `json:"name" example:"ppp0"`
	ID       string `json:"id" example:"PPPoE0"`
	Label    string `json:"label" example:"Резервный канал"`
	Up       bool   `json:"up" example:"true"`
	Priority int    `json:"priority" example:"700000"`
}

SingboxRouterWANInterfaceDTO mirrors router.WANInterfaceInfo for the WAN-binding picker.

type SingboxRouterWANInterfacesListResponse

type SingboxRouterWANInterfacesListResponse struct {
	Success bool                           `json:"success" example:"true"`
	Data    []SingboxRouterWANInterfaceDTO `json:"data"`
}

SingboxRouterWANInterfacesListResponse is the envelope for GET /singbox/router/wan-interfaces and GET /singbox/router/bindable-interfaces. For the bindable-interfaces endpoint, id and priority are always zero (only name, label, up are populated).

type SingboxStatusData

type SingboxStatusData struct {
	Installed        bool     `json:"installed" example:"true"`
	Version          string   `json:"version,omitempty" example:"1.9.3"`
	Running          bool     `json:"running" example:"true"`
	PID              int      `json:"pid,omitempty" example:"12345"`
	TunnelCount      int      `json:"tunnelCount" example:"2"`
	ProxyComponent   bool     `json:"proxyComponent" example:"true"`
	NDMSProxyEnabled bool     `json:"ndmsProxyEnabled" example:"true"`
	Features         []string `json:"features,omitempty" example:"with_quic"`
	LastError        string   `json:"lastError,omitempty" example:"+0000 2026-05-14 21:45:56 FATAL[0000] failed to initialize"`
	CurrentVersion   string   `json:"currentVersion,omitempty" example:"1.13.11"`
	RequiredVersion  string   `json:"requiredVersion" example:"1.13.11"`
	CurrentSHA256    string   `json:"currentSha256,omitempty" example:"76e67bb07b5c2bf4cef108c2f21a5ffaa684d124c21ffe220fc89b39cf1de934"`
	RequiredSHA256   string   `json:"requiredSha256,omitempty" example:"76e67bb07b5c2bf4cef108c2f21a5ffaa684d124c21ffe220fc89b39cf1de934"`
	UpdateAvailable  bool     `json:"updateAvailable" example:"false"`
	InstallState     string   `json:"installState" example:"outdated_no_space"`
	RequiredBytes    int64    `json:"requiredBytes" example:"32145678"`
	FreeBytes        int64    `json:"freeBytes" example:"8221456"`
}

SingboxStatusData mirrors frontend SingboxStatus.

type SingboxStatusResponse

type SingboxStatusResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    SingboxStatusData `json:"data"`
}

SingboxStatusResponse is the envelope for GET /singbox/status.

type SingboxTunnelConnectivity

type SingboxTunnelConnectivity struct {
	Connected bool `json:"connected" example:"true"`
	Latency   *int `json:"latency" swaggertype:"integer" example:"42"`
}

SingboxTunnelConnectivity is the connectivity field in SingboxTunnel.

type SingboxTunnelDTO

type SingboxTunnelDTO struct {
	Tag            string                    `json:"tag" example:"proxy-01"`
	Protocol       string                    `json:"protocol" example:"vless"`
	Server         string                    `json:"server" example:"proxy.example.com"`
	Port           int                       `json:"port" example:"443"`
	Security       string                    `json:"security" example:"reality"`
	Transport      string                    `json:"transport" example:"tcp"`
	ListenPort     int                       `json:"listenPort" example:"7891"`
	ProxyInterface string                    `json:"proxyInterface" example:"br0"`
	SNI            string                    `json:"sni,omitempty" example:"cdn.example.com"`
	Fingerprint    string                    `json:"fingerprint,omitempty" example:"chrome"`
	Connectivity   SingboxTunnelConnectivity `json:"connectivity"`
	Running        bool                      `json:"running" example:"true"`
}

SingboxTunnelDTO mirrors frontend SingboxTunnel.

type SingboxTunnelsResponse

type SingboxTunnelsResponse struct {
	Success bool               `json:"success" example:"true"`
	Data    []SingboxTunnelDTO `json:"data"`
}

SingboxTunnelsResponse is the envelope for GET /singbox/tunnels.

type SpeedTestInfoData

type SpeedTestInfoData struct {
	Available bool                 `json:"available" example:"true"`
	Servers   []SpeedTestServerDTO `json:"servers"`
}

SpeedTestInfoData mirrors frontend SpeedTestInfo.

type SpeedTestInfoResponse

type SpeedTestInfoResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    SpeedTestInfoData `json:"data"`
}

SpeedTestInfoResponse is the envelope for GET /test/speed/servers.

type SpeedTestResultData

type SpeedTestResultData struct {
	Server      string  `json:"server" example:"speedtest.msk.ru"`
	Direction   string  `json:"direction" example:"download"`
	Bandwidth   float64 `json:"bandwidth" example:"52428800"`
	Bytes       int64   `json:"bytes" example:"157286400"`
	Duration    float64 `json:"duration" example:"3.0"`
	Retransmits int     `json:"retransmits" example:"0"`
}

SpeedTestResultData mirrors frontend SpeedTestResult.

type SpeedTestResultResponse

type SpeedTestResultResponse struct {
	Success bool                `json:"success" example:"true"`
	Data    SpeedTestResultData `json:"data"`
}

SpeedTestResultResponse is the envelope for GET /test/speed.

type SpeedTestServerDTO

type SpeedTestServerDTO struct {
	Label string `json:"label" example:"Moscow, RU"`
	Host  string `json:"host" example:"speedtest.msk.ru"`
	Port  int    `json:"port" example:"5201"`
}

SpeedTestServerDTO mirrors frontend SpeedTestServer.

type StaticRouteDTO

type StaticRouteDTO struct {
	ID        string   `json:"id" example:"sr_001"`
	Name      string   `json:"name" example:"Office subnets"`
	TunnelID  string   `json:"tunnelID" example:"tun_abc123"`
	Subnets   []string `json:"subnets" example:"192.168.10.0/24"`
	Fallback  string   `json:"fallback,omitempty" example:""`
	IconURL   string   `json:"iconUrl,omitempty" example:"https://cdn.jsdelivr.net/gh/Koolson/Qure@master/IconSet/Color/Cloudflare.png"`
	Enabled   bool     `json:"enabled" example:"true"`
	CreatedAt string   `json:"createdAt" example:"2024-01-01T00:00:00Z"`
	UpdatedAt string   `json:"updatedAt" example:"2024-01-15T12:00:00Z"`
}

StaticRouteDTO mirrors frontend StaticRouteList.

type StaticRouteHandler

type StaticRouteHandler struct {
	// contains filtered or unexported fields
}

StaticRouteHandler handles static route API endpoints.

func NewStaticRouteHandler

func NewStaticRouteHandler(svc StaticRouteService, appLogger logging.AppLogger) *StaticRouteHandler

NewStaticRouteHandler creates a new static route handler.

func (*StaticRouteHandler) Create

Create creates a new static route list.

@Summary		Create static route
@Tags			static-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	StaticRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/static-routes/create [post]

func (*StaticRouteHandler) Delete

Delete deletes a static route list by ID.

@Summary		Delete static route
@Tags			static-routes
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"List id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/static-routes/delete [post]

func (*StaticRouteHandler) Import

Import imports subnets from a .bat file content.

@Summary		Import static routes
@Tags			static-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	StaticRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/static-routes/import [post]

func (*StaticRouteHandler) List

List returns static IP routes.

@Summary		List static routes
@Tags			static-routes
@Produce		json
@Security		CookieAuth
@Success		200	{object}	StaticRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/static-routes/list [get]
@Router			/routing/static-routes [get]

func (*StaticRouteHandler) SetEnabled

func (h *StaticRouteHandler) SetEnabled(w http.ResponseWriter, r *http.Request)

SetEnabled toggles the enabled state of a static route list.

@Summary		Set static route enabled
@Tags			static-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"List id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/static-routes/set-enabled [post]

func (*StaticRouteHandler) SetEventBus

func (h *StaticRouteHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus for SSE publishing.

func (*StaticRouteHandler) Update

Update updates an existing static route list.

@Summary		Update static route
@Tags			static-routes
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	StaticRoutesListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/static-routes/update [post]

type StaticRouteService

type StaticRouteService interface {
	List() ([]storage.StaticRouteList, error)
	Get(id string) (*storage.StaticRouteList, error)
	Create(ctx context.Context, rl storage.StaticRouteList) (*storage.StaticRouteList, error)
	Update(ctx context.Context, rl storage.StaticRouteList) (*storage.StaticRouteList, error)
	Delete(ctx context.Context, id string) error
	SetEnabled(ctx context.Context, id string, enabled bool) error
	Import(ctx context.Context, tunnelID, name, batContent string) (*storage.StaticRouteList, error)
}

StaticRouteService defines what the static route handler needs from the service.

type StaticRoutesListResponse

type StaticRoutesListResponse struct {
	Success bool             `json:"success" example:"true"`
	Data    []StaticRouteDTO `json:"data"`
}

StaticRoutesListResponse is the envelope for GET /static-routes/list.

type SubgroupsData

type SubgroupsData struct {
	Group     string   `json:"group" example:"routing"`
	Subgroups []string `json:"subgroups"`
}

SubgroupsData is the payload for GET /logs/subgroups.

type SubgroupsResponseEnvelope

type SubgroupsResponseEnvelope struct {
	Success bool          `json:"success" example:"true"`
	Data    SubgroupsData `json:"data"`
}

SubgroupsResponseEnvelope is the envelope for GET /logs/subgroups.

type SubscriptionDTO

type SubscriptionDTO struct {
	ID              string                    `json:"id" example:"sub-demo"`
	Label           string                    `json:"label" example:"Demo Provider"`
	URL             string                    `json:"url" example:"https://example.com/subscriptions/demo.txt"`
	IsInline        bool                      `json:"isInline" example:"false"`
	Headers         []SubscriptionHeader      `json:"headers"`
	RefreshHours    int                       `json:"refreshHours" example:"24"`
	LastFetched     string                    `json:"lastFetched" example:"2026-05-14T21:30:00Z"`
	LastError       string                    `json:"lastError,omitempty" example:""`
	SelectorTag     string                    `json:"selectorTag" example:"sub-demo"`
	InboundTag      string                    `json:"inboundTag" example:"sub-demo-in"`
	ListenPort      int                       `json:"listenPort" example:"11000"`
	ProxyIndex      int                       `` /* 310-byte string literal not displayed */
	MemberTags      []string                  `json:"memberTags" example:"sub-demo-001,sub-demo-002,sub-demo-003"`
	Members         []SubscriptionMemberDTO   `json:"members"`
	OrphanTags      []string                  `json:"orphanTags" example:""`
	RejectedMembers []SubscriptionRejectedDTO `json:"rejectedMembers"`
	InfoItems       []SubscriptionInfoItemDTO `json:"infoItems"`
	ActiveMember    string                    `json:"activeMember" example:"sub-demo-001"`
	ExcludedTags    []string                  `json:"excludedTags"`
	ExcludedMembers []SubscriptionMemberDTO   `json:"excludedMembers,omitempty"`
	Enabled         bool                      `json:"enabled" example:"true"`
	Mode            string                    `json:"mode" example:"selector"`
	URLTest         *SubscriptionURLTestDTO   `json:"urlTest,omitempty"`
}

SubscriptionDTO mirrors subscription.Subscription for OpenAPI exposure.

Inline content is deliberately NOT exposed: pasted share-links carry the full server address + UUID/password and would otherwise leak into every list-all response (i.e. every page load), browser DevTools, and any reverse-proxy access log that records response bodies. Frontend only needs IsInline to gate UI affordances; raw paste stays server-side until a future single-record endpoint requires it.

type SubscriptionHandler

type SubscriptionHandler struct {
	// contains filtered or unexported fields
}

SubscriptionHandler exposes /api/singbox/subscriptions/* endpoints.

func NewSubscriptionHandler

func NewSubscriptionHandler(svc *subscription.Service, presence SingboxPresenceProbe, appLogger ...logging.AppLogger) *SubscriptionHandler

func (*SubscriptionHandler) ActiveMember

func (h *SubscriptionHandler) ActiveMember(w http.ResponseWriter, r *http.Request)

ActiveMember handles POST /api/singbox/subscriptions/active-member?id=

@Summary		Set active member
@Tags			subscriptions
@Accept			json
@Produce		json
@Param			id	query		string				true	"subscription id"
@Param			req	body		ActiveMemberRequest	true	"member tag"
@Success		200	{object}	SubscriptionResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/active-member [post]

func (*SubscriptionHandler) ActiveNow

func (h *SubscriptionHandler) ActiveNow(w http.ResponseWriter, r *http.Request)

ActiveNow handles GET /api/singbox/subscriptions/active-now?id=

@Summary		Live active member from Clash
@Description	Returns the currently-active member tag as reported by the running sing-box Clash API. For urltest mode this reflects the auto-selected fastest member. Empty `now` means Clash is unreachable or no member selected yet.
@Tags			subscriptions
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Subscription id"
@Success		200	{object}	OkResponse{data=ActiveNowResponse}
@Failure		400	{object}	APIErrorEnvelope
@Failure		404	{object}	APIErrorEnvelope
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/active-now [get]

func (*SubscriptionHandler) AddMember

func (h *SubscriptionHandler) AddMember(w http.ResponseWriter, r *http.Request)

AddMember handles POST /api/singbox/subscriptions/members/add?id=

@Summary		Add a manual member to an inline subscription
@Tags			subscriptions
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		query		string			true	"Subscription ID"
@Param			body	body		AddMemberRequest	true	"Share-link"
@Success		200		{object}	SubscriptionResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		409		{object}	APIErrorEnvelope
@Failure		422		{object}	APIErrorEnvelope	"sing-box validation rejected the new member"
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/members/add [post]

func (*SubscriptionHandler) Create

Create handles POST /api/singbox/subscriptions/create

@Summary		Create sing-box subscription
@Description	Creates subscription from URL or inline share links. Returns 422 VALIDATION_FAILED when the merged sing-box config is rejected by `sing-box check` (e.g. reality outbound without uTLS).
@Tags			subscriptions
@Accept			json
@Produce		json
@Param			req	body		CreateSubscriptionRequest	true	"create request"
@Success		200	{object}	SubscriptionResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		412	{object}	APIErrorEnvelope
@Failure		422	{object}	APIErrorEnvelope	"sing-box validation rejected the subscription"
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/create [post]

func (*SubscriptionHandler) Delete

Delete handles DELETE /api/singbox/subscriptions/delete?id= Always performs full cleanup (no cascade flag).

@Summary		Delete sing-box subscription
@Tags			subscriptions
@Produce		json
@Param			id	query		string	true	"subscription id"
@Success		200	{object}	APIEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/delete [delete]

func (*SubscriptionHandler) ExcludeMembers

func (h *SubscriptionHandler) ExcludeMembers(w http.ResponseWriter, r *http.Request)

ExcludeMembers handles POST /api/singbox/subscriptions/members/exclude?id=

@Summary		Exclude members from a URL subscription
@Description	Marks the given member tags as excluded: rebuilds the
@Description	selector without them, drops their outbounds and survives
@Description	refresh. Reversible via restore. URL subscriptions only.
@Tags			subscriptions
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		query		string					true	"Subscription ID"
@Param			body	body		ExcludeMembersRequest	true	"Member tags to exclude"
@Success		200		{object}	SubscriptionResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		409		{object}	APIErrorEnvelope	"excluding all members is not allowed"
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/members/exclude [post]

func (*SubscriptionHandler) Get

Get handles GET /api/singbox/subscriptions/get?id=

@Summary		Get sing-box subscription
@Tags			subscriptions
@Produce		json
@Param			id	query		string	true	"subscription id"
@Success		200	{object}	SubscriptionResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		404	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/get [get]

func (*SubscriptionHandler) GetStream

func (h *SubscriptionHandler) GetStream(w http.ResponseWriter, r *http.Request)

GetStream handles GET /api/singbox/subscriptions/get-stream?id=

@Summary		Stream subscription details progressively (SSE)
@Description	Streams a subscription as Server-Sent Events: one `meta` event with subscription header (incl. total member count), then one `member` event per server member, then a `done` event with finalisation (orphans, active member). Frontend uses this to show a progress bar and render cards as they arrive instead of waiting for the full payload. Service.Get is itself sync; the streaming is the handler's contract — it writes events with Flush() between, so TCP + browser deliver progressively.
@Tags			subscriptions
@Produce		text/event-stream
@Security		CookieAuth
@Param			id	query	string	true	"Subscription id"
@Success		200	"Stream of SSE events: meta, member×N, done"
@Failure		400	{object}	APIErrorEnvelope
@Failure		404	{object}	APIErrorEnvelope
@Failure		405	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/get-stream [get]

func (*SubscriptionHandler) InfoRemove

func (h *SubscriptionHandler) InfoRemove(w http.ResponseWriter, r *http.Request)

InfoRemove handles POST /api/singbox/subscriptions/info/remove?id=

func (*SubscriptionHandler) List

List handles GET /api/singbox/subscriptions

@Summary		List sing-box subscriptions
@Description	Returns configured subscriptions with parsed members. Mocked examples include vless/reality members.
@Tags			subscriptions
@Produce		json
@Success		200	{object}	SubscriptionListResponse
@Failure		405	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions [get]

func (*SubscriptionHandler) OrphansDelete

func (h *SubscriptionHandler) OrphansDelete(w http.ResponseWriter, r *http.Request)

OrphansDelete handles POST /api/singbox/subscriptions/orphans/delete?id=

@Summary		Delete orphan members from subscription
@Tags			subscriptions
@Produce		json
@Param			id	query		string	true	"subscription id"
@Success		200	{object}	SubscriptionResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/orphans/delete [post]

func (*SubscriptionHandler) PreviewURL

func (h *SubscriptionHandler) PreviewURL(w http.ResponseWriter, r *http.Request)

PreviewURL handles POST /api/singbox/subscriptions/preview

@Summary		Preview a subscription URL without creating it
@Description	Read-only fetch + parse of a subscription URL. Returns the
@Description	parsed members (with subID-independent keys) so the import
@Description	wizard can offer per-server exclusion before creation.
@Tags			subscriptions
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			body	body		PreviewURLRequest	true	"URL and optional headers"
@Success		200		{object}	APIEnvelope
@Failure		400		{object}	APIErrorEnvelope
@Failure		502		{object}	APIErrorEnvelope	"fetch or parse failed"
@Router			/singbox/subscriptions/preview [post]

func (*SubscriptionHandler) Refresh

Refresh handles POST /api/singbox/subscriptions/refresh?id=

@Summary		Refresh sing-box subscription
@Description	Re-fetches the provider URL and re-runs Pass 1 / Pass 2 validation. Returns 422 VALIDATION_FAILED when the refreshed config is rejected by `sing-box check`.
@Tags			subscriptions
@Produce		json
@Param			id	query		string	false	"subscription id"
@Success		200	{object}	SubscriptionResponse
@Failure		422	{object}	APIErrorEnvelope	"sing-box validation rejected the refreshed subscription"
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/refresh [post]

func (*SubscriptionHandler) RejectedToInfo

func (h *SubscriptionHandler) RejectedToInfo(w http.ResponseWriter, r *http.Request)

RejectedToInfo handles POST /api/singbox/subscriptions/rejected/to-info?id=

func (*SubscriptionHandler) RemoveMember

func (h *SubscriptionHandler) RemoveMember(w http.ResponseWriter, r *http.Request)

RemoveMember handles POST /api/singbox/subscriptions/members/remove?id=

@Summary		Remove a member from an inline subscription
@Description	Removing the last member tears the whole subscription
@Description	down. The response indicates whether the subscription
@Description	itself was deleted via deleted=true.
@Tags			subscriptions
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		query		string				true	"Subscription ID"
@Param			body	body		RemoveMemberRequest	true	"Member tag"
@Success		200		{object}	APIEnvelope
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		422		{object}	APIErrorEnvelope	"sing-box validation rejected the remainder"
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/members/remove [post]

func (*SubscriptionHandler) RestoreMembers

func (h *SubscriptionHandler) RestoreMembers(w http.ResponseWriter, r *http.Request)

RestoreMembers handles POST /api/singbox/subscriptions/members/restore?id=

@Summary		Restore previously excluded members
@Description	Removes the given tags from the exclusion set and runs a
@Description	refresh, re-materializing the returned servers.
@Tags			subscriptions
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id		query		string					true	"Subscription ID"
@Param			body	body		RestoreMembersRequest	true	"Member tags to restore"
@Success		200		{object}	SubscriptionResponse
@Failure		400		{object}	APIErrorEnvelope
@Failure		404		{object}	APIErrorEnvelope
@Failure		500		{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/members/restore [post]

func (*SubscriptionHandler) SetNDMSProxyToggler

func (h *SubscriptionHandler) SetNDMSProxyToggler(s ndmsProxyToggler)

SetNDMSProxyToggler wires the global NDMS Proxy flag reader. When wired and the flag is false, DTO converters surface proxyIndex=-1 so the UI (and any other API consumer) sees that the composite NDMS Proxy is gone — same shape contract as ListTunnels uses for tunnel ProxyInterface fields. Without this setter, every subscription DTO surfaces the stored ProxyIndex unconditionally.

func (*SubscriptionHandler) SetOutboundRefCheckers

SetOutboundRefCheckers wires device-proxy and router reference guards for subscription deletion (refuse when selector/members are still referenced).

func (*SubscriptionHandler) Update

Update handles PUT /api/singbox/subscriptions/update?id=

@Summary		Update sing-box subscription
@Description	Updates subscription metadata or refreshes from a new URL. Returns 422 VALIDATION_FAILED when the new merged config is rejected by `sing-box check`.
@Tags			subscriptions
@Accept			json
@Produce		json
@Param			id	query		string						true	"subscription id"
@Param			req	body		UpdateSubscriptionRequest	true	"update request"
@Success		200	{object}	SubscriptionResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		422	{object}	APIErrorEnvelope	"sing-box validation rejected the subscription"
@Failure		500	{object}	APIErrorEnvelope
@Router			/singbox/subscriptions/update [put]

type SubscriptionHeader

type SubscriptionHeader struct {
	Name  string `json:"name" example:"User-Agent"`
	Value string `json:"value" example:"Happ/4.6.0"`
}

SubscriptionHeader is a single custom HTTP header for the fetch request.

type SubscriptionInfoItemDTO

type SubscriptionInfoItemDTO struct {
	ID     string `json:"id"`
	Label  string `json:"label"`
	Tag    string `json:"tag,omitempty"`
	Source string `json:"source,omitempty"`
}

SubscriptionInfoItemDTO is a provider info banner (not a proxy).

type SubscriptionListResponse

type SubscriptionListResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    []SubscriptionDTO `json:"data"`
}

SubscriptionListResponse is the envelope for GET /api/singbox/subscriptions.

type SubscriptionMemberDTO

type SubscriptionMemberDTO struct {
	Tag       string `json:"tag" example:"sub-abc12345-aabbccdd"`
	Label     string `json:"label,omitempty" example:"🇺🇸 LA-1"`
	Protocol  string `json:"protocol" example:"vless"`
	Server    string `json:"server" example:"de01.example.com"`
	Port      int    `json:"port" example:"443"`
	SNI       string `json:"sni,omitempty" example:"de01.example.com"`
	Transport string `json:"transport,omitempty" example:"ws"`
	Security  string `json:"security,omitempty" example:"tls"`
}

SubscriptionMemberDTO carries per-member parsed metadata for the UI.

type SubscriptionMetaDTO

type SubscriptionMetaDTO struct {
	ID              string                    `json:"id"`
	Label           string                    `json:"label"`
	URL             string                    `json:"url"`
	IsInline        bool                      `json:"isInline"`
	Headers         []SubscriptionHeader      `json:"headers"`
	RefreshHours    int                       `json:"refreshHours"`
	LastFetched     string                    `json:"lastFetched" example:"2026-05-14T21:30:00Z"`
	LastError       string                    `json:"lastError,omitempty" example:""`
	SelectorTag     string                    `json:"selectorTag"`
	InboundTag      string                    `json:"inboundTag"`
	ListenPort      int                       `json:"listenPort"`
	ProxyIndex      int                       `json:"proxyIndex" description:"See SubscriptionDTO.ProxyIndex — gated identically (-1 when NDMS Proxy disabled)."`
	Enabled         bool                      `json:"enabled"`
	Mode            string                    `json:"mode"`
	URLTest         *SubscriptionURLTestDTO   `json:"urlTest,omitempty"`
	Total           int                       `json:"total"`
	RejectedMembers []SubscriptionRejectedDTO `json:"rejectedMembers"`
	InfoItems       []SubscriptionInfoItemDTO `json:"infoItems"`
}

SubscriptionMetaDTO is the meta-event payload for the streaming /get-stream endpoint. Mirrors SubscriptionDTO minus Members (those arrive as separate member events). The `total` field tells the UI how many member events to expect for progress.

type SubscriptionRejectedDTO

type SubscriptionRejectedDTO struct {
	Tag      string `json:"tag,omitempty"`
	Label    string `json:"label,omitempty"`
	Protocol string `json:"protocol,omitempty"`
	Server   string `json:"server,omitempty"`
	Port     int    `json:"port,omitempty"`
	Reason   string `json:"reason"`
}

SubscriptionRejectedDTO is a parsed share-link that was not added to sing-box.

type SubscriptionResponse

type SubscriptionResponse struct {
	Success bool            `json:"success" example:"true"`
	Data    SubscriptionDTO `json:"data"`
}

SubscriptionResponse is the envelope for single-subscription responses.

type SubscriptionStreamDoneDTO

type SubscriptionStreamDoneDTO struct {
	OrphanTags      []string                  `json:"orphanTags"`
	ActiveMember    string                    `json:"activeMember"`
	RejectedMembers []SubscriptionRejectedDTO `json:"rejectedMembers"`
	InfoItems       []SubscriptionInfoItemDTO `json:"infoItems"`
	ExcludedTags    []string                  `json:"excludedTags"`
	ExcludedMembers []SubscriptionMemberDTO   `json:"excludedMembers,omitempty"`
}

SubscriptionStreamDoneDTO is the done-event payload — finalisation fields that don't fit the meta header but the frontend needs to complete the rendering.

type SubscriptionStreamMemberDTO

type SubscriptionStreamMemberDTO struct {
	Index  int                   `json:"index"`
	Member SubscriptionMemberDTO `json:"member"`
}

SubscriptionStreamMemberDTO wraps a single member with its index for the member-event payload. Index lets the frontend reason about progress (i+1 / total) and detect gaps if events arrive out of order.

type SubscriptionURLTestDTO

type SubscriptionURLTestDTO struct {
	URL         string `json:"url" example:"https://www.gstatic.com/generate_204"`
	IntervalSec int    `json:"intervalSec" example:"60"`
	ToleranceMs int    `json:"toleranceMs" example:"50"`
}

SubscriptionURLTestDTO carries urltest tuning. Only meaningful when SubscriptionDTO.Mode == "urltest"; when absent or mode is "selector" the consumer should ignore it.

type SuggestAddressData

type SuggestAddressData struct {
	Address string `json:"address" example:"10.10.0.1"`
	Mask    string `json:"mask" example:"255.255.255.0"`
}

SuggestAddressData carries a free private /24 for the create-server UI.

type SuggestAddressResponse

type SuggestAddressResponse struct {
	Success bool               `json:"success" example:"true"`
	Data    SuggestAddressData `json:"data"`
}

SuggestAddressResponse is the envelope for GET /managed-servers/suggest-address.

type SystemHandler

type SystemHandler struct {
	// contains filtered or unexported fields
}

SystemHandler handles system information endpoints.

func NewSystemHandler

func NewSystemHandler(version string) *SystemHandler

NewSystemHandler creates a new system handler.

func (*SystemHandler) AllInterfaces

func (h *SystemHandler) AllInterfaces(w http.ResponseWriter, r *http.Request)

AllInterfaces returns all router interfaces for routing configuration. GET /api/system/all-interfaces

@Summary		All interfaces
@Tags			system
@Produce		json
@Security		CookieAuth
@Success		200	{object}	AllInterfacesResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/all-interfaces [get]

func (*SystemHandler) BuildSystemInfo

func (h *SystemHandler) BuildSystemInfo() map[string]interface{}

BuildSystemInfo returns system info for SSE snapshot.

func (*SystemHandler) HydraRouteControl

func (h *SystemHandler) HydraRouteControl(w http.ResponseWriter, r *http.Request)

HydraRouteControl starts/stops/restarts the HydraRoute daemon.

@Summary		HydraRoute control (system)
@Tags			system
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/hydraroute-control [post]

func (*SystemHandler) HydraRouteStatus

func (h *SystemHandler) HydraRouteStatus(w http.ResponseWriter, r *http.Request)

HydraRouteStatus returns HydraRoute Neo detection status.

@Summary		HydraRoute status (system)
@Tags			system
@Produce		json
@Security		CookieAuth
@Success		200	{object}	HydraRouteStatusResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/hydraroute-status [get]

func (*SystemHandler) Info

func (h *SystemHandler) Info(w http.ResponseWriter, r *http.Request)

Info returns system information.

@Summary		System info
@Tags			system
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SystemInfoResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/info [get]

func (*SystemHandler) RestartDaemon

func (h *SystemHandler) RestartDaemon(w http.ResponseWriter, r *http.Request)

RestartDaemon triggers a self-restart of the AWG Manager daemon.

@Summary		Restart daemon
@Tags			system
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/restart [post]

func (*SystemHandler) SetActiveBackend

func (h *SystemHandler) SetActiveBackend(b backend.Backend)

SetActiveBackend sets the active backend for status reporting.

func (*SystemHandler) SetBootStatusFunc

func (h *SystemHandler) SetBootStatusFunc(fn func() bool)

SetBootStatusFunc sets the callback to check if boot is in progress.

func (*SystemHandler) SetEventBus

func (h *SystemHandler) SetEventBus(bus *events.Bus)

SetEventBus wires the SSE bus so HR Neo control actions emit `routing.hydrarouteStatus` resource:invalidated hints.

func (*SystemHandler) SetHydraRoute

func (h *SystemHandler) SetHydraRoute(svc *hydraroute.Service)

SetHydraRoute sets the HydraRoute Neo service for status/control endpoints.

func (*SystemHandler) SetKmodLoader

func (h *SystemHandler) SetKmodLoader(l KmodLoader)

SetKmodLoader sets the kernel module loader for status reporting.

func (*SystemHandler) SetNDMSQueries

func (h *SystemHandler) SetNDMSQueries(q *ndmsquery.Queries)

SetNDMSQueries sets the NDMS query registry for the new CQRS layer.

func (*SystemHandler) SetPingCheckService

func (h *SystemHandler) SetPingCheckService(svc PingCheckService)

SetPingCheckService sets the ping check service for stopping monitoring on restart.

func (*SystemHandler) SetRestartFunc

func (h *SystemHandler) SetRestartFunc(fn func())

SetRestartFunc sets the callback to trigger daemon self-restart.

func (*SystemHandler) SetSettingsStore

func (h *SystemHandler) SetSettingsStore(sp SettingsProvider)

SetSettingsStore sets the settings provider.

func (*SystemHandler) SetSettingsWriter

func (h *SystemHandler) SetSettingsWriter(sw *storage.SettingsStore)

SetSettingsWriter sets the writable settings store for saving.

func (*SystemHandler) SetSingboxOperator

func (h *SystemHandler) SetSingboxOperator(op *singbox.Operator)

SetSingboxOperator provides access to the sing-box operator for reporting install status in system info.

func (*SystemHandler) SetSlowRequestThresholdMs

func (h *SystemHandler) SetSlowRequestThresholdMs(ms int)

SetSlowRequestThresholdMs exposes the -slow-request-ms runtime flag to the UI.

func (*SystemHandler) SetTunnelService

func (h *SystemHandler) SetTunnelService(svc TunnelService)

SetTunnelService sets the tunnel service for stopping tunnels on backend change.

func (*SystemHandler) WANInterfaces

func (h *SystemHandler) WANInterfaces(w http.ResponseWriter, r *http.Request)

WANInterfaces returns available WAN interfaces for routing. GET /api/system/wan-interfaces

@Summary		WAN interfaces
@Tags			system
@Produce		json
@Security		CookieAuth
@Success		200	{object}	WANInterfacesResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/wan-interfaces [get]

type SystemInfoBackendAvailability

type SystemInfoBackendAvailability struct {
	Nativewg bool `json:"nativewg" example:"true"`
	Kernel   bool `json:"kernel" example:"false"`
}

SystemInfoBackendAvailability shows which tunnel backends are available.

type SystemInfoData

type SystemInfoData struct {
	Version                     string                        `json:"version" example:"2.5.0"`
	GoVersion                   string                        `json:"goVersion" example:"go1.23.0"`
	GoArch                      string                        `json:"goArch" example:"arm64"`
	GoOS                        string                        `json:"goOS" example:"linux"`
	KeeneticOS                  string                        `json:"keeneticOS" example:"ndms"`
	IsOS5                       bool                          `json:"isOS5" example:"true"`
	FirmwareVersion             string                        `json:"firmwareVersion" example:"4.2.1"`
	SupportsExtendedASC         bool                          `json:"supportsExtendedASC" example:"true"`
	SupportsHRanges             bool                          `json:"supportsHRanges" example:"true"`
	SupportsPingCheck           bool                          `json:"supportsPingCheck" example:"true"`
	TotalMemoryMB               int                           `json:"totalMemoryMB" example:"512"`
	IsLowMemory                 bool                          `json:"isLowMemory" example:"false"`
	GcMemLimit                  string                        `json:"gcMemLimit" example:"128MiB"`
	Gogc                        string                        `json:"gogc" example:"25"`
	DisableMemorySaving         bool                          `json:"disableMemorySaving" example:"false"`
	KernelModuleExists          bool                          `json:"kernelModuleExists" example:"true"`
	KernelModuleLoaded          bool                          `json:"kernelModuleLoaded" example:"false"`
	KernelModuleModel           string                        `json:"kernelModuleModel" example:"MT7981"`
	KernelModuleVersion         string                        `json:"kernelModuleVersion" example:""`
	IsAarch64                   bool                          `json:"isAarch64" example:"true"`
	ActiveBackend               string                        `json:"activeBackend" example:"nativewg"`
	RouterIP                    string                        `json:"routerIP" example:"192.168.1.1"`
	RouterTime                  string                        `json:"routerTime" example:"2026-05-20T14:32:10+03:00"`
	RouterTimezone              string                        `json:"routerTimezone" example:"MSK"`
	RouterTimezoneOffsetMinutes int                           `json:"routerTimezoneOffsetMinutes" example:"180"`
	BootInProgress              bool                          `json:"bootInProgress" example:"false"`
	SlowRequestThresholdMs      int                           `json:"slowRequestThresholdMs" example:"0"`
	BackendAvailability         SystemInfoBackendAvailability `json:"backendAvailability"`
	Singbox                     SystemInfoSingbox             `json:"singbox"`
	RouterDetails               *RouterDetails                `json:"routerDetails,omitempty"`
}

SystemInfoData is the payload returned by GET /system/info.

type SystemInfoResponse

type SystemInfoResponse struct {
	Success bool           `json:"success" example:"true"`
	Data    SystemInfoData `json:"data"`
}

SystemInfoResponse is the envelope for GET /system/info.

type SystemInfoSingbox

type SystemInfoSingbox struct {
	Installed bool   `json:"installed" example:"true"`
	Version   string `json:"version" example:"1.9.3"`
}

SystemInfoSingbox shows sing-box component info embedded in system info.

type SystemTunnelDTO

type SystemTunnelDTO struct {
	ID            string               `json:"id" example:"Wireguard0"`
	InterfaceName string               `json:"interfaceName" example:"Wireguard0"`
	Description   string               `json:"description" example:"Home Server"`
	Status        string               `json:"status" example:"up"`
	Connected     bool                 `json:"connected" example:"true"`
	MTU           int                  `json:"mtu" example:"1420"`
	Peer          *SystemTunnelPeerDTO `json:"peer,omitempty"`
}

SystemTunnelDTO mirrors frontend SystemTunnel.

type SystemTunnelPeerDTO

type SystemTunnelPeerDTO struct {
	PublicKey     string `json:"publicKey" example:"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC="`
	Endpoint      string `json:"endpoint" example:"vpn2.example.com:51820"`
	RxBytes       int64  `json:"rxBytes" example:"2097152"`
	TxBytes       int64  `json:"txBytes" example:"1048576"`
	LastHandshake string `json:"lastHandshake" example:"2024-01-15T10:30:00Z"`
	Online        bool   `json:"online" example:"true"`
}

SystemTunnelPeerDTO mirrors the peer field in SystemTunnel.

type SystemTunnelsHandler

type SystemTunnelsHandler struct {
	// contains filtered or unexported fields
}

SystemTunnelsHandler handles system WireGuard tunnel operations.

func NewSystemTunnelsHandler

func NewSystemTunnelsHandler(svc systemtunnel.Service, settings *storage.SettingsStore, awgStore *storage.AWGTunnelStore, appLogger logging.AppLogger) *SystemTunnelsHandler

NewSystemTunnelsHandler creates a new system tunnels handler.

func (*SystemTunnelsHandler) ASC

ASC handles ASC parameter operations. GET /api/system-tunnels/asc?name=X — read params POST /api/system-tunnels/asc?name=X — write params

func (*SystemTunnelsHandler) CheckConnectivity

func (h *SystemTunnelsHandler) CheckConnectivity(w http.ResponseWriter, r *http.Request)

CheckConnectivity performs connectivity test through system tunnel. GET /api/system-tunnels/test-connectivity?name=Wireguard0

func (*SystemTunnelsHandler) CheckIP

CheckIP tests IP through system tunnel. GET /api/system-tunnels/test-ip?name=Wireguard0&service=optional

func (*SystemTunnelsHandler) Get

Get returns a single system WireGuard tunnel. GET /api/system-tunnels/get?name=Wireguard0

func (*SystemTunnelsHandler) List

List returns all visible (non-hidden) system WireGuard tunnels. GET /api/system-tunnels

@Summary		List system tunnels
@Description	Returns all WireGuard tunnels managed by the router OS itself (non-hidden).
@Tags			system-tunnels
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SystemTunnelsResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/system-tunnels [get]

func (*SystemTunnelsHandler) SpeedTestStream

func (h *SystemTunnelsHandler) SpeedTestStream(w http.ResponseWriter, r *http.Request)

SpeedTestStream runs iperf3 speed test with SSE streaming through system tunnel. GET /api/system-tunnels/test-speed?name=Wireguard0&server=X&port=N&direction=download|upload

type SystemTunnelsResponse

type SystemTunnelsResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    []SystemTunnelDTO `json:"data"`
}

SystemTunnelsResponse is the envelope for GET /system-tunnels.

type TakeGeoFileControlRequest

type TakeGeoFileControlRequest struct {
	Path string `json:"path" example:"/opt/etc/HydraRoute/geosite_GA.dat"`
}

TakeGeoFileControlRequest is the body for POST /hydraroute/geo-files/take-control.

type TerminalHandler

type TerminalHandler struct {
	// contains filtered or unexported fields
}

TerminalHandler handles terminal API endpoints.

func NewTerminalHandler

func NewTerminalHandler(manager terminal.Manager, log logging.AppLogger) *TerminalHandler

NewTerminalHandler creates a new terminal handler.

func (*TerminalHandler) Install

func (h *TerminalHandler) Install(w http.ResponseWriter, r *http.Request)

Install installs ttyd via opkg. POST /api/terminal/install

@Summary		Install terminal (ttyd)
@Tags			terminal
@Produce		json
@Security		CookieAuth
@Success		200	{object}	TerminalStatusResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/terminal/install [post]

func (*TerminalHandler) Start

Start launches the ttyd process. POST /api/terminal/start

@Summary		Start terminal
@Tags			terminal
@Produce		json
@Security		CookieAuth
@Success		200	{object}	TerminalStartResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/terminal/start [post]

func (*TerminalHandler) Status

func (h *TerminalHandler) Status(w http.ResponseWriter, r *http.Request)

Status returns the current terminal state. GET /api/terminal/status

@Summary		Terminal status
@Tags			terminal
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope{data=TerminalStatusResponse}
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/terminal/status [get]

func (*TerminalHandler) Stop

Stop kills the ttyd process. POST /api/terminal/stop

@Summary		Stop terminal
@Tags			terminal
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/terminal/stop [post]

func (*TerminalHandler) WebSocket

func (h *TerminalHandler) WebSocket(w http.ResponseWriter, r *http.Request)

WebSocket proxies WebSocket connection to ttyd. GET /api/terminal/ws

@Summary		Terminal WebSocket
@Tags			terminal
@Produce		json
@Security		CookieAuth
@Success		200	{string}	string	"WebSocket upgrade"
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/terminal/ws [get]

type TerminalStartData

type TerminalStartData struct {
	Port int `json:"port" example:"7681"`
}

TerminalStartData is the payload returned by POST /terminal/start.

type TerminalStartResponse

type TerminalStartResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    TerminalStartData `json:"data"`
}

TerminalStartResponse is the envelope for POST /terminal/start.

type TerminalStatusResponse

type TerminalStatusResponse struct {
	Installed     bool `json:"installed" example:"true"`
	Running       bool `json:"running" example:"false"`
	SessionActive bool `json:"sessionActive" example:"false"`
}

TerminalStatusResponse represents terminal status.

type TestingHandler

type TestingHandler struct {
	// contains filtered or unexported fields
}

TestingHandler handles tunnel testing operations.

func NewTestingHandler

func NewTestingHandler(testingService *testing.Service) *TestingHandler

NewTestingHandler creates a new testing handler.

func (*TestingHandler) CheckConnectivity

func (h *TestingHandler) CheckConnectivity(w http.ResponseWriter, r *http.Request)

CheckConnectivity performs a quick connectivity test through tunnel.

@Summary		Connectivity check
@Tags			test
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	ConnectivityResultResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/test/connectivity [get]

func (*TestingHandler) CheckIP

func (h *TestingHandler) CheckIP(w http.ResponseWriter, r *http.Request)

CheckIP tests if traffic goes through tunnel by comparing IPs.

@Summary		IP leak check
@Tags			test
@Produce		json
@Security		CookieAuth
@Param			id		query	string	false	"Tunnel id"
@Param			service	query	string	false	"Check service id"
@Success		200	{object}	IPResultResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/test/ip [get]

func (*TestingHandler) IPCheckServices

func (h *TestingHandler) IPCheckServices(w http.ResponseWriter, r *http.Request)

IPCheckServices returns the list of available IP check services.

@Summary		IP check services
@Tags			test
@Produce		json
@Security		CookieAuth
@Success		200	{object}	IPServicesResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/test/ip/services [get]

func (*TestingHandler) SpeedTest

func (h *TestingHandler) SpeedTest(w http.ResponseWriter, r *http.Request)

SpeedTest runs iperf3 speed test through a tunnel.

@Summary		Speed test (sync)
@Tags			test
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SpeedTestResultResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/test/speed [get]

func (*TestingHandler) SpeedTestServers

func (h *TestingHandler) SpeedTestServers(w http.ResponseWriter, r *http.Request)

SpeedTestServers returns iperf3 availability and server list.

@Summary		Speed test servers
@Tags			test
@Produce		json
@Security		CookieAuth
@Success		200	{object}	SpeedTestInfoResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/test/speed/servers [get]

func (*TestingHandler) SpeedTestStream

func (h *TestingHandler) SpeedTestStream(w http.ResponseWriter, r *http.Request)

SpeedTestStream runs iperf3 speed test with SSE streaming of per-second intervals.

@Summary		Speed test stream
@Tags			test
@Produce		text/event-stream
@Security		CookieAuth
@Success		200	{string}	string	"SSE stream"
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/test/speed/stream [get]

type ToggleNDMSProxyRequest

type ToggleNDMSProxyRequest struct {
	Enabled bool `json:"enabled"`
}

ToggleNDMSProxyRequest is the body for POST /singbox/ndms-proxy.

type TunnelConnectionInfoDTO

type TunnelConnectionInfoDTO struct {
	Name      string `json:"name" example:"My VPN"`
	Interface string `json:"interface" example:"nwg0"`
	Count     int    `json:"count" example:"12"`
}

TunnelConnectionInfoDTO mirrors frontend TunnelConnectionInfo.

type TunnelControlData

type TunnelControlData struct {
	ID     string `json:"id" example:"tun_abc123"`
	Status string `json:"status" example:"connected"`
}

TunnelControlData is the response data for control operations (start/stop/restart).

type TunnelControlResponse

type TunnelControlResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    TunnelControlData `json:"data"`
}

TunnelControlResponse is the envelope for control operations.

type TunnelDeleteResponse

type TunnelDeleteResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    TunnelDeleteResultData `json:"data"`
}

TunnelDeleteResponse is the envelope for POST /tunnels/delete.

type TunnelDeleteResultData

type TunnelDeleteResultData struct {
	Success  bool   `json:"success" example:"true"`
	TunnelId string `json:"tunnelId" example:"tun_abc123"`
	Verified bool   `json:"verified" example:"true"`
}

TunnelDeleteResultData mirrors frontend DeleteResult.

type TunnelDetailResponse

type TunnelDetailResponse struct {
	Success bool         `json:"success" example:"true"`
	Data    AWGTunnelDTO `json:"data"`
}

TunnelDetailResponse is the envelope for GET /tunnels/get.

type TunnelHookInvalidator

type TunnelHookInvalidator func(ctx context.Context)

TunnelHookInvalidator is invoked on ifcreated / ifdestroyed hooks so the handler can drop stale NDMS caches and publish a `resource:invalidated` hint for the tunnels resource. Every connected SSE client then refetches `/api/tunnels/all` and the UI drops/adds tunnel cards without a browser refresh.

type TunnelListItemDTO

type TunnelListItemDTO struct {
	ID                        string                `json:"id" example:"tun_abc123"`
	Name                      string                `json:"name" example:"My AWG Tunnel"`
	Type                      string                `json:"type" example:"awg" enums:"awg,wg"`
	Status                    string                `json:"status" example:"connected" enums:"connected,disconnected,error,disabled"`
	Enabled                   bool                  `json:"enabled" example:"true"`
	DefaultRoute              bool                  `json:"defaultRoute" example:"false"`
	Endpoint                  string                `json:"endpoint" example:"vpn.example.com:51820"`
	Address                   string                `json:"address" example:"10.0.0.2/32"`
	InterfaceName             string                `json:"interfaceName,omitempty" example:"nwg0"`
	NdmsName                  string                `json:"ndmsName,omitempty" example:"Wireguard0"`
	Backend                   string                `json:"backend,omitempty" example:"nativewg" enums:"nativewg,kernel"`
	AWGVersion                string                `json:"awgVersion,omitempty" example:"awg2.0" enums:"wg,awg1.0,awg1.5,awg2.0"`
	MTU                       int                   `json:"mtu,omitempty" example:"1420"`
	IspInterface              string                `json:"ispInterface,omitempty" example:"PPPoE0"`
	IspInterfaceLabel         string                `json:"ispInterfaceLabel,omitempty" example:"WAN"`
	ResolvedIspInterface      string                `json:"resolvedIspInterface,omitempty" example:"PPPoE0"`
	ResolvedIspInterfaceLabel string                `json:"resolvedIspInterfaceLabel,omitempty" example:"WAN"`
	HasAddressConflict        bool                  `json:"hasAddressConflict,omitempty" example:"false"`
	StartedAt                 string                `json:"startedAt,omitempty" example:"2024-01-15T10:00:00Z"`
	RxBytes                   int64                 `json:"rxBytes,omitempty" example:"10485760"`
	TxBytes                   int64                 `json:"txBytes,omitempty" example:"5242880"`
	LastHandshake             string                `json:"lastHandshake,omitempty" example:"2024-01-15T10:30:00Z"`
	PingCheck                 TunnelPingCheckStatus `json:"pingCheck"`
}

TunnelListItemDTO mirrors frontend TunnelListItem.

type TunnelListResponse

type TunnelListResponse struct {
	Success bool                `json:"success" example:"true"`
	Data    []TunnelListItemDTO `json:"data"`
}

TunnelListResponse is the envelope for GET /tunnels/list.

type TunnelPingCheckStatus

type TunnelPingCheckStatus struct {
	Status        string `json:"status" example:"alive"`
	RestartCount  int    `json:"restartCount" example:"0"`
	FailCount     int    `json:"failCount" example:"0"`
	FailThreshold int    `json:"failThreshold" example:"3"`
}

TunnelPingCheckStatus is the ping-check status embedded in TunnelListItem.

type TunnelPingStatusDTO

type TunnelPingStatusDTO struct {
	TunnelId      string `json:"tunnelId" example:"tun_abc123"`
	TunnelName    string `json:"tunnelName" example:"My VPN"`
	Enabled       bool   `json:"enabled" example:"true"`
	Backend       string `json:"backend" example:"nativewg"`
	Status        string `json:"status" example:"alive"`
	Method        string `json:"method" example:"http"`
	LastLatency   int    `json:"lastLatency" example:"35"`
	FailCount     int    `json:"failCount" example:"0"`
	FailThreshold int    `json:"failThreshold" example:"3"`
	RestartCount  int    `json:"restartCount" example:"0"`
}

TunnelPingStatusDTO mirrors frontend TunnelPingStatus.

type TunnelReferencedDetails

type TunnelReferencedDetails struct {
	TunnelID    string   `json:"tunnelId" example:"tun-a"`
	DeviceProxy bool     `json:"deviceProxy" example:"false"`
	RouterRules []int    `json:"routerRules"`
	RouterOther []string `json:"routerOther"`
}

TunnelReferencedDetails describes where a tunnel is still referenced when deletion is refused (HTTP 409).

type TunnelReferencedResponse

type TunnelReferencedResponse struct {
	Error   string                  `json:"error" example:"tunnel_referenced"`
	Details TunnelReferencedDetails `json:"details"`
}

TunnelReferencedResponse is the HTTP 409 body for POST /tunnels/delete when the tunnel's outbound tag is still referenced by sing-box router or device-proxy config. The client uses details to deeplink the user to the referencing configuration.

type TunnelService

type TunnelService interface {
	// CRUD
	List(ctx context.Context) ([]service.TunnelWithStatus, error)
	Get(ctx context.Context, tunnelID string) (*service.TunnelWithStatus, error)
	Create(ctx context.Context, tunnelID, name string, cfg tunnel.Config, stored *storage.AWGTunnel) error
	Update(ctx context.Context, oldStored, newStored *storage.AWGTunnel) error
	Delete(ctx context.Context, tunnelID string) error

	// Lifecycle (delegated to orchestrator)
	Start(ctx context.Context, tunnelID string) error
	Stop(ctx context.Context, tunnelID string) error
	Restart(ctx context.Context, tunnelID string) error

	// Validation
	CheckAddressConflicts(ctx context.Context, tunnelID string) []string

	// State
	GetState(ctx context.Context, tunnelID string) tunnel.StateInfo

	// Settings
	SetEnabled(ctx context.Context, tunnelID string, enabled bool) error
	SetDefaultRoute(ctx context.Context, tunnelID string, enabled bool) error

	// Import
	Import(ctx context.Context, confContent, name, backend string) (*service.TunnelWithStatus, error)

	// ReplaceConfig replaces a tunnel's config from a new .conf file.
	ReplaceConfig(ctx context.Context, tunnelID, confContent, newName string) error

	// WAN state model
	WANModel() *wan.Model

	// Resolved ISP for auto-mode tunnels
	GetResolvedISP(tunnelID string) string

	// SetSelfCreateGate wires the gate used by import/create paths to
	// suppress hook-driven snapshot refreshes while an NDMS interface is
	// being created but our store.Save hasn't run yet.
	SetSelfCreateGate(g tunnel.SelfCreateGater)
}

TunnelService defines the interface for tunnel operations used by API handlers.

type TunnelStateInfoDTO

type TunnelStateInfoDTO struct {
	State          int    `json:"state" example:"3"`
	InterfaceUp    bool   `json:"interfaceUp" example:"true"`
	ProcessRunning bool   `json:"processRunning" example:"true"`
	HasHandshake   bool   `json:"hasHandshake" example:"true"`
	LastHandshake  string `json:"lastHandshake" example:"2024-01-15T10:30:00Z"`
	RxBytes        int64  `json:"rxBytes" example:"10485760"`
	TxBytes        int64  `json:"txBytes" example:"5242880"`
}

TunnelStateInfoDTO mirrors frontend TunnelStateInfo.

type TunnelTrafficData

type TunnelTrafficData struct {
	Points []TunnelTrafficPoint `json:"points"`
	Stats  TunnelTrafficStats   `json:"stats"`
}

TunnelTrafficData is the payload for GET /tunnels/traffic.

type TunnelTrafficPoint

type TunnelTrafficPoint struct {
	T  int64 `json:"t" example:"1705312200"`
	Rx int64 `json:"rx" example:"1024000"`
	Tx int64 `json:"tx" example:"512000"`
}

TunnelTrafficPoint is one point in a traffic chart.

type TunnelTrafficResponse

type TunnelTrafficResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    TunnelTrafficData `json:"data"`
}

TunnelTrafficResponse is the envelope for GET /tunnels/traffic.

type TunnelTrafficStats

type TunnelTrafficStats struct {
	Points    int     `json:"points" example:"60"`
	PeakRate  float64 `json:"peakRate" example:"1048576"`
	AvgRx     float64 `json:"avgRx" example:"524288"`
	AvgTx     float64 `json:"avgTx" example:"262144"`
	CurrentRx float64 `json:"currentRx" example:"102400"`
	CurrentTx float64 `json:"currentTx" example:"51200"`
	// VolumeRx is estimated bytes received over the selected window (Σ rxRate×Δt on raw history samples).
	VolumeRx int64 `json:"volumeRx" example:"1073741824"`
	// VolumeTx is estimated bytes sent over the selected window (Σ txRate×Δt on raw history samples).
	VolumeTx int64 `json:"volumeTx" example:"536870912"`
}

TunnelTrafficStats are aggregate stats for a traffic period.

type TunnelsAllResponse

type TunnelsAllResponse struct {
	Success bool                   `json:"success" example:"true"`
	Data    TunnelsAllSnapshotData `json:"data"`
}

TunnelsAllResponse is the envelope for GET /tunnels/all.

type TunnelsAllSnapshotData

type TunnelsAllSnapshotData struct {
	Tunnels  []TunnelListItemDTO `json:"tunnels"`
	External []ExternalTunnelDTO `json:"external"`
	System   []SystemTunnelDTO   `json:"system"`
}

TunnelsAllSnapshotData is the payload for GET /tunnels/all.

type TunnelsHandler

type TunnelsHandler struct {
	// contains filtered or unexported fields
}

TunnelsHandler handles tunnel CRUD operations.

func NewTunnelsHandler

func NewTunnelsHandler(svc TunnelService, store *storage.AWGTunnelStore, appLogger logging.AppLogger) *TunnelsHandler

NewTunnelsHandler creates a new tunnels handler.

func (*TunnelsHandler) Create

func (h *TunnelsHandler) Create(w http.ResponseWriter, r *http.Request)

Create creates a new tunnel.

@Summary		Create tunnel
@Tags			tunnels
@Accept			json
@Produce		json
@Security		CookieAuth
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/create [post]

func (*TunnelsHandler) Delete

func (h *TunnelsHandler) Delete(w http.ResponseWriter, r *http.Request)

Delete deletes a tunnel.

@Summary		Delete tunnel
@Tags			tunnels
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	TunnelDeleteResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		409	{object}	TunnelReferencedResponse
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/delete [post]

func (*TunnelsHandler) Export

func (h *TunnelsHandler) Export(w http.ResponseWriter, r *http.Request)

Export returns a single tunnel config as a downloadable .conf file.

@Summary		Export tunnel config
@Tags			tunnels
@Produce		plain
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{file}	binary
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/export [get]

func (*TunnelsHandler) ExportAll

func (h *TunnelsHandler) ExportAll(w http.ResponseWriter, r *http.Request)

ExportAll returns all tunnel configs as a downloadable ZIP archive.

@Summary		Export all tunnels (zip)
@Tags			tunnels
@Produce		application/zip
@Security		CookieAuth
@Success		200	{file}	binary
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/export-all [get]

func (*TunnelsHandler) Get

Get returns a single tunnel by ID.

@Summary		Get tunnel
@Tags			tunnels
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	TunnelDetailResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/get [get]

func (*TunnelsHandler) GetAll

func (h *TunnelsHandler) GetAll(w http.ResponseWriter, r *http.Request)

GetAll returns the composite tunnels snapshot ({tunnels, external, system}) the frontend polls instead of listening to a legacy snapshot SSE event. GET /api/tunnels/all

@Summary		Composite tunnels snapshot
@Tags			tunnels
@Produce		json
@Security		CookieAuth
@Success		200	{object}	TunnelsAllResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/all [get]

func (*TunnelsHandler) List

List returns all tunnels.

@Summary		List tunnels
@Tags			tunnels
@Produce		json
@Security		CookieAuth
@Success		200	{object}	TunnelListResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/list [get]

func (*TunnelsHandler) PublishTunnelList

func (h *TunnelsHandler) PublishTunnelList(ctx context.Context)

PublishTunnelList emits resource:invalidated hints for tunnels and routing.tunnels so polling stores refetch immediately. Exported for cross-handler use (Import, ExternalAdopt, Control).

func (*TunnelsHandler) ReplaceConf

func (h *TunnelsHandler) ReplaceConf(w http.ResponseWriter, r *http.Request)

ReplaceConf replaces a tunnel's configuration from a new .conf file. If the tunnel is running, it is stopped before replacement and restarted after.

@Summary		Replace tunnel from conf
@Tags			tunnels
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/replace [post]

func (*TunnelsHandler) SetCatalog

func (h *TunnelsHandler) SetCatalog(cat routing.Catalog)

SetCatalog sets the routing catalog for tunnel list updates.

func (*TunnelsHandler) SetEventBus

func (h *TunnelsHandler) SetEventBus(bus *events.Bus)

SetEventBus sets the event bus for SSE publishing.

func (*TunnelsHandler) SetOrchestrator

func (h *TunnelsHandler) SetOrchestrator(orch *orchestrator.Orchestrator)

SetOrchestrator sets the orchestrator for lifecycle operations.

func (*TunnelsHandler) SetPingCheckService

func (h *TunnelsHandler) SetPingCheckService(svc PingCheckService)

SetPingCheckService sets the ping check service for monitoring control.

func (*TunnelsHandler) SetPingCheckSnapshot

func (h *TunnelsHandler) SetPingCheckSnapshot(fn func())

SetPingCheckSnapshot sets the function that publishes a pingcheck snapshot.

func (*TunnelsHandler) SetSelfCreateGate

func (h *TunnelsHandler) SetSelfCreateGate(g tunnel.SelfCreateGater)

SetSelfCreateGate wires the gate used to suppress hook-driven snapshot refreshes while the handler itself is creating an NDMS interface (manual Create path — import path gates inside ServiceImpl directly).

func (*TunnelsHandler) SetSettingsStore

func (h *TunnelsHandler) SetSettingsStore(store *storage.SettingsStore)

SetSettingsStore sets the settings store for reading defaults.

func (*TunnelsHandler) SetTrafficHistory

func (h *TunnelsHandler) SetTrafficHistory(th *traffic.History)

SetTrafficHistory sets the traffic history accumulator.

func (*TunnelsHandler) SetTunnelsSnapshotBuilder

func (h *TunnelsHandler) SetTunnelsSnapshotBuilder(fn func(ctx context.Context) map[string]interface{})

SetTunnelsSnapshotBuilder wires the composer used by GetAll and mutation handlers that return fresh snapshot state. Server.go typically injects TunnelsSnapshotBuilder.Build.

func (*TunnelsHandler) Traffic

func (h *TunnelsHandler) Traffic(w http.ResponseWriter, r *http.Request)

Traffic returns rate history + aggregates for a single tunnel. GET /api/tunnels/traffic?id=<tunnelID>&period=5m|10m|30m|1h|3h|6h|12h|24h

Only a fixed set of short/long-range presets is accepted — anything else returns 400. 1h is what the card chart fetches on mount to backfill before SSE takes over; the detail modal can request any of the supported presets.

data.stats.volumeRx and data.stats.volumeTx are byte estimates for the selected window from raw in-memory samples (zero if fewer than two samples).

@Summary		Tunnel traffic history
@Tags			tunnels
@Produce		json
@Security		CookieAuth
@Param			id		query	string	true	"Tunnel id"
@Param			period	query	string	true	"5m, 10m, 30m, 1h, 3h, 6h, 12h, or 24h"
@Success		200	{object}	TunnelTrafficResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/traffic [get]

func (*TunnelsHandler) Update

func (h *TunnelsHandler) Update(w http.ResponseWriter, r *http.Request)

Update updates an existing tunnel.

@Summary		Update tunnel
@Tags			tunnels
@Accept			json
@Produce		json
@Security		CookieAuth
@Param			id	query	string	true	"Tunnel id"
@Success		200	{object}	APIEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/tunnels/update [post]

type TunnelsSnapshotBuilder

type TunnelsSnapshotBuilder struct {
	// contains filtered or unexported fields
}

TunnelsSnapshotBuilder composes the {tunnels, external, system} payload used by GET /api/tunnels/all and by the hook-driven resource:invalidated refresher. It is the single assembly point for the composite tunnels list the polling store reads.

The struct is a thin holder for the three handler references; callers wire whichever handlers are available and call Build. Missing handlers produce empty slices in the relevant keys.

func NewTunnelsSnapshotBuilder

func NewTunnelsSnapshotBuilder() *TunnelsSnapshotBuilder

NewTunnelsSnapshotBuilder creates a new builder with no handlers wired. Use the setters to wire the three handler references.

func (*TunnelsSnapshotBuilder) Build

func (b *TunnelsSnapshotBuilder) Build(ctx context.Context) map[string]interface{}

Build composes the snapshot payload for the polling store. Returns nil when no TunnelsHandler is wired, or when its listItems call errors — in both cases there's nothing safe to return.

func (*TunnelsSnapshotBuilder) SetExternalHandler

func (b *TunnelsSnapshotBuilder) SetExternalHandler(h *ExternalTunnelsHandler)

SetExternalHandler sets the external tunnels handler reference.

func (*TunnelsSnapshotBuilder) SetSystemTunnelsHandler

func (b *TunnelsSnapshotBuilder) SetSystemTunnelsHandler(h *SystemTunnelsHandler)

SetSystemTunnelsHandler sets the system tunnels handler reference.

func (*TunnelsSnapshotBuilder) SetTunnelsHandler

func (b *TunnelsSnapshotBuilder) SetTunnelsHandler(h *TunnelsHandler)

SetTunnelsHandler sets the tunnels handler reference.

type UpdateApplyData

type UpdateApplyData struct {
	Status string `json:"status" example:"updating"`
}

UpdateApplyData is the payload for POST /system/update/apply.

type UpdateApplyResponse

type UpdateApplyResponse struct {
	Success bool            `json:"success" example:"true"`
	Data    UpdateApplyData `json:"data"`
}

UpdateApplyResponse is the envelope for POST /system/update/apply.

type UpdateCheckResponse

type UpdateCheckResponse struct {
	Success bool           `json:"success" example:"true"`
	Data    UpdateInfoData `json:"data"`
}

UpdateCheckResponse is the envelope for GET /system/update/check.

type UpdateGeoFileRequest

type UpdateGeoFileRequest struct {
	Path  string            `json:"path" example:"/opt/etc/hrneo/geosite.db"`
	Route *DownloadRouteDTO `json:"route,omitempty"`
}

UpdateGeoFileRequest is the body for POST /hydraroute/geo-files/update. Empty path triggers a bulk refresh of every tracked geo file.

type UpdateHandler

type UpdateHandler struct {
	// contains filtered or unexported fields
}

UpdateHandler handles update check and apply endpoints.

func NewUpdateHandler

func NewUpdateHandler(updater *updater.Service, appLogger logging.AppLogger) *UpdateHandler

NewUpdateHandler creates a new update handler.

func (*UpdateHandler) Apply

func (h *UpdateHandler) Apply(w http.ResponseWriter, r *http.Request)

Apply starts the opkg upgrade process. POST /api/system/update/apply

@Summary		Apply system update
@Tags			update
@Produce		json
@Security		CookieAuth
@Success		200	{object}	UpdateApplyResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/update/apply [post]

func (*UpdateHandler) Changelog

func (h *UpdateHandler) Changelog(w http.ResponseWriter, r *http.Request)

Changelog returns changelog entries. Two modes:

  • Range: from and to both supplied → entries in (from, to], newest-first.
  • Minor line: only to supplied → all entries with the same major.minor as to, with version <= to (used when no upgrade is pending).

GET /api/system/update/changelog?from=2.7.5&to=2.8.0 GET /api/system/update/changelog?to=2.8.1

@Summary		Update changelog
@Tags			update
@Produce		json
@Security		CookieAuth
@Param			from	query	string	false	"From version"
@Param			to		query	string	true	"To version"
@Success		200	{object}	ChangelogResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/update/changelog [get]

func (*UpdateHandler) Check

func (h *UpdateHandler) Check(w http.ResponseWriter, r *http.Request)

Check returns cached update info or triggers a fresh check. GET /api/system/update/check?force=true

@Summary		Update check
@Tags			update
@Produce		json
@Security		CookieAuth
@Param			force	query	boolean	false	"Force refresh from upstream"
@Success		200	{object}	UpdateCheckResponse
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/system/update/check [get]

type UpdateInfoData

type UpdateInfoData struct {
	Available      bool   `json:"available" example:"false"`
	CurrentVersion string `json:"currentVersion" example:"2.5.0"`
	LatestVersion  string `json:"latestVersion,omitempty" example:"2.5.0"`
	CheckedAt      string `json:"checkedAt" example:"2024-01-15T10:00:00Z"`
	Checking       bool   `json:"checking" example:"false"`
}

UpdateInfoData mirrors frontend UpdateInfo.

type UpdatePeerRequestDTO

type UpdatePeerRequestDTO struct {
	Description string `json:"description" example:"My Phone"`
	TunnelIP    string `json:"tunnelIP" example:"10.10.0.2/32"`
	DNS         string `json:"dns,omitempty" example:"8.8.8.8"`
}

UpdatePeerRequestDTO is the swagger-visible body for PUT /managed-servers/{id}/peers/{pubkey}.

type UpdateServerRequestDTO

type UpdateServerRequestDTO struct {
	Address     string  `json:"address" example:"10.10.0.1"`
	Mask        string  `json:"mask" example:"255.255.255.0"`
	ListenPort  int     `json:"listenPort" example:"51821"`
	Description *string `json:"description,omitempty" example:"My WG Server"`
	Endpoint    *string `json:"endpoint,omitempty" example:"203.0.113.42:51821"`
	DNS         *string `json:"dns,omitempty" example:"8.8.8.8"`
	MTU         *int    `json:"mtu,omitempty" example:"1420"`
}

UpdateServerRequestDTO is the swagger-visible body for PUT /managed-servers/{id}.

type UpdateSettingsDTO

type UpdateSettingsDTO struct {
	CheckEnabled bool   `json:"checkEnabled" example:"true"`
	Channel      string `json:"channel" example:"stable" enums:"stable,develop"`
}

UpdateSettingsDTO mirrors frontend UpdateSettings.

type UpdateSubscriptionRequest

type UpdateSubscriptionRequest struct {
	Label        *string                 `json:"label,omitempty" example:"Demo Provider Updated"`
	URL          *string                 `json:"url,omitempty" example:"https://example.com/subscriptions/demo.txt"`
	Headers      *[]SubscriptionHeader   `json:"headers,omitempty"`
	RefreshHours *int                    `json:"refreshHours,omitempty"`
	Enabled      *bool                   `json:"enabled,omitempty"`
	Mode         *string                 `json:"mode,omitempty" example:"selector"`
	URLTest      *SubscriptionURLTestDTO `json:"urlTest,omitempty"`
}

UpdateSubscriptionRequest is the body for PUT /api/singbox/subscriptions/update. All fields are optional; absent fields leave the stored value unchanged.

type WANHandler

type WANHandler struct {
	// contains filtered or unexported fields
}

WANHandler serves WAN status queries. WAN up/down events are handled by HookHandler (/api/hook/ndms layer=ipv4).

func NewWANHandler

func NewWANHandler(svc TunnelService, appLogger logging.AppLogger) *WANHandler

NewWANHandler creates a new WAN status handler.

func (*WANHandler) GetStatus

func (h *WANHandler) GetStatus(w http.ResponseWriter, r *http.Request)

GetStatus returns current WAN interface state. GET /api/wan/status

@Summary		WAN status
@Tags			wan
@Produce		json
@Security		CookieAuth
@Success		200	{object}	WANStatusEnvelope
@Failure		400	{object}	APIErrorEnvelope
@Failure		500	{object}	APIErrorEnvelope
@Router			/wan/status [get]

type WANIPData

type WANIPData struct {
	IP string `json:"ip" example:"203.0.113.42"`
}

WANIPData is the data for GET /servers/wan-ip.

type WANIPResponse

type WANIPResponse struct {
	Success bool      `json:"success" example:"true"`
	Data    WANIPData `json:"data"`
}

WANIPResponse is the envelope for GET /servers/wan-ip.

type WANInterfaceDTO

type WANInterfaceDTO struct {
	Name  string `json:"name" example:"ISP1"`
	Label string `json:"label" example:"Home Internet"`
	State string `json:"state" example:"up"`
}

WANInterfaceDTO mirrors frontend WANInterface.

type WANInterfaceStatusDTO

type WANInterfaceStatusDTO struct {
	Up    bool   `json:"up" example:"true"`
	Label string `json:"label" example:"Home Internet"`
}

WANInterfaceStatusDTO is a single WAN interface status.

type WANInterfaceStatusItemDTO

type WANInterfaceStatusItemDTO struct {
	Up    bool   `json:"up" example:"true"`
	Label string `json:"label" example:"Home Internet"`
}

WANInterfaceStatusDTO is a single WAN interface status entry.

type WANInterfacesResponse

type WANInterfacesResponse struct {
	Success bool              `json:"success" example:"true"`
	Data    []WANInterfaceDTO `json:"data"`
}

WANInterfacesResponse is the envelope for GET /system/wan-interfaces.

type WANStatusEnvelope

type WANStatusEnvelope struct {
	Success bool `json:"success" example:"true"`
	Data    struct {
		AnyWANUp bool `json:"anyWANUp" example:"true"`
	} `json:"data"`
}

WANStatusEnvelope is the swagger-friendly envelope for GET /wan/status. (The actual handler uses the local WANStatusResponse which embeds wanpkg types.)

type WANStatusResponse

type WANStatusResponse struct {
	Interfaces map[string]wanpkg.InterfaceStatus `json:"interfaces"`
	AnyWANUp   bool                              `json:"anyWANUp"`
}

WANStatusResponse is the response format for WAN status queries.

type WireguardServerConfigDTO

type WireguardServerConfigDTO struct {
	PublicKey  string                         `json:"publicKey" example:"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE="`
	ListenPort int                            `json:"listenPort" example:"51820"`
	MTU        int                            `json:"mtu" example:"1420"`
	Address    string                         `json:"address" example:"10.0.1.1"`
	Peers      []WireguardServerConfigPeerDTO `json:"peers"`
}

WireguardServerConfigDTO mirrors frontend WireguardServerConfig.

type WireguardServerConfigPeerDTO

type WireguardServerConfigPeerDTO struct {
	PublicKey    string   `json:"publicKey" example:"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD="`
	Description  string   `json:"description" example:"Phone"`
	PresharedKey string   `json:"presharedKey" example:"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG="`
	AllowedIPs   []string `json:"allowedIPs" example:"10.0.1.2/32"`
	Address      string   `json:"address" example:"10.0.1.2"`
}

WireguardServerConfigPeerDTO mirrors frontend WireguardServerPeerConfig.

type WireguardServerDTO

type WireguardServerDTO struct {
	ID            string                   `json:"id" example:"Wireguard0"`
	InterfaceName string                   `json:"interfaceName" example:"Wireguard0"`
	Description   string                   `json:"description" example:"Wireguard VPN Server"`
	Status        string                   `json:"status" example:"up"`
	Connected     bool                     `json:"connected" example:"true"`
	MTU           int                      `json:"mtu" example:"1420"`
	Address       string                   `json:"address" example:"10.0.1.1"`
	Mask          string                   `json:"mask" example:"255.255.255.0"`
	PublicKey     string                   `json:"publicKey" example:"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE="`
	ListenPort    int                      `json:"listenPort" example:"51820"`
	Peers         []WireguardServerPeerDTO `json:"peers"`
	NATEnabled    bool                     `json:"natEnabled,omitempty" example:"true"`
	NATMode       string                   `json:"natMode,omitempty" example:"full"`
	Policy        string                   `json:"policy,omitempty" example:"Policy0"`
	KeenDNSDomain string                   `json:"keenDnsDomain,omitempty" example:"home.keenetic.pro"`
	// Endpoint is the user-configured connect host for client .conf files.
	// Empty = WAN IP at generation time.
	Endpoint string `json:"endpoint,omitempty" example:"203.0.113.42"`
	BuiltIn  bool   `json:"builtIn,omitempty" example:"true"`
	// NATModeKnown/PolicyKnown are false when the corresponding NDMS read
	// failed (e.g. transient router error). The frontend must render an
	// "unknown" state instead of trusting the zero-valued NATMode/Policy,
	// which would otherwise read as a fabricated "none".
	NATModeKnown bool `json:"natModeKnown" example:"true"`
	PolicyKnown  bool `json:"policyKnown" example:"true"`
	// Enabled reflects NDMS admin intent (summary.layer.conf == "running").
	// Status/connected alone are unreliable for the on/off toggle.
	Enabled      bool `json:"enabled" example:"true"`
	EnabledKnown bool `json:"enabledKnown" example:"true"`
}

WireguardServerDTO mirrors frontend WireguardServer.

type WireguardServerPeerDTO

type WireguardServerPeerDTO struct {
	PublicKey     string   `json:"publicKey" example:"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD="`
	Description   string   `json:"description" example:"Phone"`
	Endpoint      string   `json:"endpoint" example:"1.2.3.4:12345"`
	AllowedIPs    []string `json:"allowedIPs" example:"10.0.1.2/32"`
	RxBytes       int64    `json:"rxBytes" example:"1048576"`
	TxBytes       int64    `json:"txBytes" example:"524288"`
	LastHandshake string   `json:"lastHandshake" example:"2024-01-15T10:30:00Z"`
	Online        bool     `json:"online" example:"true"`
	Enabled       bool     `json:"enabled" example:"true"`
	ConfAvailable bool     `json:"confAvailable,omitempty" example:"true"`
}

WireguardServerPeerDTO mirrors frontend WireguardServerPeer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL