mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-13 09:36:05 +00:00
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (push) Waiting to run
137 lines
4.3 KiB
Go
137 lines
4.3 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/xray"
|
|
)
|
|
|
|
// LocalDeps wires the runtime to the panel's xray process and the
|
|
// service.XrayService restart trigger via callbacks. We use callbacks
|
|
// (not an interface to *service.XrayService) because the runtime
|
|
// package would otherwise cycle-import service.
|
|
type LocalDeps struct {
|
|
// APIPort returns the xray gRPC API port the local engine is
|
|
// currently listening on. Returns 0 when xray isn't running yet —
|
|
// callers should treat that as a transient error.
|
|
APIPort func() int
|
|
// SetNeedRestart trips the panel's "restart xray on next cron tick"
|
|
// flag. Mirrors how InboundController.addInbound calls
|
|
// xrayService.SetToNeedRestart() today.
|
|
SetNeedRestart func()
|
|
}
|
|
|
|
// Local implements Runtime against the panel's own xray process. Each
|
|
// call follows the existing inbound.go pattern: open a gRPC client,
|
|
// run one operation, close. Per-call init keeps the connection state
|
|
// scoped so a stuck call can't leak across operations.
|
|
type Local struct {
|
|
deps LocalDeps
|
|
|
|
// Serialise gRPC operations — xray's HandlerService isn't documented
|
|
// as concurrent-safe and the existing InboundService implicitly
|
|
// runs one op at a time per request. This matches that.
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewLocal builds a Local runtime. deps.APIPort and deps.SetNeedRestart
|
|
// are required; callers that want a no-op restart can pass `func(){}`.
|
|
func NewLocal(deps LocalDeps) *Local {
|
|
return &Local{deps: deps}
|
|
}
|
|
|
|
func (l *Local) Name() string { return "local" }
|
|
|
|
// withAPI runs fn against a freshly-initialised XrayAPI client and
|
|
// guarantees Close() afterwards. Returns an error if the gRPC port
|
|
// isn't available yet (xray still starting / stopped).
|
|
func (l *Local) withAPI(fn func(api *xray.XrayAPI) error) error {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
port := l.deps.APIPort()
|
|
if port <= 0 {
|
|
return errors.New("local xray is not running")
|
|
}
|
|
var api xray.XrayAPI
|
|
if err := api.Init(port); err != nil {
|
|
return err
|
|
}
|
|
defer api.Close()
|
|
return fn(&api)
|
|
}
|
|
|
|
func (l *Local) AddInbound(_ context.Context, ib *model.Inbound) error {
|
|
body, err := json.MarshalIndent(ib.GenXrayInboundConfig(), "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return l.withAPI(func(api *xray.XrayAPI) error {
|
|
return api.AddInbound(body)
|
|
})
|
|
}
|
|
|
|
func (l *Local) DelInbound(_ context.Context, ib *model.Inbound) error {
|
|
return l.withAPI(func(api *xray.XrayAPI) error {
|
|
return api.DelInbound(ib.Tag)
|
|
})
|
|
}
|
|
|
|
func (l *Local) UpdateInbound(ctx context.Context, oldIb, newIb *model.Inbound) error {
|
|
// xray-core has no in-place inbound update — drop and re-add.
|
|
// Matches what InboundService.UpdateInbound did inline.
|
|
if err := l.DelInbound(ctx, oldIb); err != nil {
|
|
// Best-effort: continue to AddInbound so a transient remove
|
|
// failure (e.g. inbound already gone) doesn't strand us. The
|
|
// caller's needRestart fallback will reconcile from config.
|
|
_ = err
|
|
}
|
|
if !newIb.Enable {
|
|
// Disabled inbounds aren't pushed to xray; we already removed
|
|
// the old one above.
|
|
return nil
|
|
}
|
|
return l.AddInbound(ctx, newIb)
|
|
}
|
|
|
|
func (l *Local) AddUser(_ context.Context, ib *model.Inbound, userMap map[string]any) error {
|
|
return l.withAPI(func(api *xray.XrayAPI) error {
|
|
return api.AddUser(string(ib.Protocol), ib.Tag, userMap)
|
|
})
|
|
}
|
|
|
|
func (l *Local) RemoveUser(_ context.Context, ib *model.Inbound, email string) error {
|
|
return l.withAPI(func(api *xray.XrayAPI) error {
|
|
return api.RemoveUser(ib.Tag, email)
|
|
})
|
|
}
|
|
|
|
func (l *Local) RestartXray(_ context.Context) error {
|
|
if l.deps.SetNeedRestart != nil {
|
|
l.deps.SetNeedRestart()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Reset methods are intentional no-ops for Local. The central DB UPDATE
|
|
// that runs in InboundService.Reset* before this call has already zeroed
|
|
// the counters that xray reads; on the next stats poll the gRPC service
|
|
// will pick up matching values. Pre-Phase-1 the panel never issued an
|
|
// xrayApi reset call here either — keeping the same shape avoids a
|
|
// behaviour change for single-panel users.
|
|
|
|
func (l *Local) ResetClientTraffic(_ context.Context, _ *model.Inbound, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
func (l *Local) ResetInboundClientTraffics(_ context.Context, _ *model.Inbound) error {
|
|
return nil
|
|
}
|
|
|
|
func (l *Local) ResetAllTraffics(_ context.Context) error {
|
|
return nil
|
|
}
|