mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 15:55:13 +08:00
🚀 Sync 2026-07-21 20:34:38
This commit is contained in:
1 parent
9f2092b4f2
commit
87d970d38f
7 files changed
+646
-10
No files matched your search
+1
-1
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=daed
|
PKG_NAME:=daed
|
||||||
PKG_VERSION:=2026.07.20
|
PKG_VERSION:=2026.07.20
|
||||||
PKG_RELEASE:=26
|
PKG_RELEASE:=27
|
||||||
|
|
||||||
PKG_SOURCE:=daed-src-2026.07.20-42db711ac8ce.tar.gz
|
PKG_SOURCE:=daed-src-2026.07.20-42db711ac8ce.tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
||||||
|
|||||||
@@ -0,0 +1,619 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: kenzok8 <kenzok8@gmail.com>
|
||||||
|
Date: Tue, 21 Jul 2026 02:53:42 +0800
|
||||||
|
Subject: [PATCH] daed: strictly reconcile subscription updates
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
订阅更新按节点链接精确对账,普通组里的失效节点删除,fixed 组固定节点转成独立节点保留。
|
||||||
|
|
||||||
|
数据库提交后按运行状态应用配置,补齐并发地址校验、导入失败回滚和 reload 超时恢复。
|
||||||
|
---
|
||||||
|
cmd/run.go | 6 +-
|
||||||
|
graphql/mutation.go | 9 +-
|
||||||
|
graphql/root_schema.go | 2 +-
|
||||||
|
graphql/service/config/mutation_utils.go | 59 ++++-
|
||||||
|
.../service/subscription/mutation_utils.go | 181 +++++++++++----
|
||||||
|
.../subscription/mutation_utils_test.go | 212 ++++++++++++++++++
|
||||||
|
6 files changed, 408 insertions(+), 61 deletions(-)
|
||||||
|
create mode 100644 graphql/service/subscription/mutation_utils_test.go
|
||||||
|
|
||||||
|
diff --git a/cmd/run.go b/cmd/run.go
|
||||||
|
index 7dce577..978f46c 100644
|
||||||
|
--- a/cmd/run.go
|
||||||
|
+++ b/cmd/run.go
|
||||||
|
@@ -161,11 +161,8 @@ func restoreRunningState() (err error) {
|
||||||
|
if !reload {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
- tx := db.BeginTx(context.TODO())
|
||||||
|
// Reload.
|
||||||
|
- if _, err = config.Run(tx, false); err != nil {
|
||||||
|
- tx.Rollback()
|
||||||
|
-
|
||||||
|
+ if _, err = config.Run(context.TODO(), false); err != nil {
|
||||||
|
// Another tx.
|
||||||
|
// Set running = false.
|
||||||
|
tx2 := db.BeginTx(context.TODO())
|
||||||
|
@@ -183,7 +180,6 @@ func restoreRunningState() (err error) {
|
||||||
|
tx2.Commit()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
- tx.Commit()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/graphql/mutation.go b/graphql/mutation.go
|
||||||
|
index fcbf440..adb747c 100644
|
||||||
|
--- a/graphql/mutation.go
|
||||||
|
+++ b/graphql/mutation.go
|
||||||
|
@@ -256,14 +256,7 @@ func (r *MutationResolver) SelectConfig(args *struct {
|
||||||
|
func (r *MutationResolver) Run(args *struct {
|
||||||
|
Dry bool
|
||||||
|
}) (int32, error) {
|
||||||
|
- tx := db.BeginTx(context.TODO())
|
||||||
|
- ret, err := config.Run(tx, args.Dry)
|
||||||
|
- if err != nil {
|
||||||
|
- tx.Rollback()
|
||||||
|
- return 0, err
|
||||||
|
- }
|
||||||
|
- tx.Commit()
|
||||||
|
- return ret, nil
|
||||||
|
+ return config.Run(context.TODO(), args.Dry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MutationResolver) CreateDns(args *struct {
|
||||||
|
diff --git a/graphql/root_schema.go b/graphql/root_schema.go
|
||||||
|
index 8b82db6..a96cace 100644
|
||||||
|
--- a/graphql/root_schema.go
|
||||||
|
+++ b/graphql/root_schema.go
|
||||||
|
@@ -121,7 +121,7 @@ type Mutation {
|
||||||
|
# tagSubscription is to give the subscription a new tag.
|
||||||
|
tagSubscription(id: ID!, tag: String!): Int! @hasRole(role: ADMIN)
|
||||||
|
|
||||||
|
- # updateSubscription is to re-fetch subscription and resolve subscription into nodes. Old nodes that independently belong to any groups will not be removed.
|
||||||
|
+ # updateSubscription re-fetches and strictly reconciles subscription nodes. Stale nodes pinned by fixed groups become independent nodes.
|
||||||
|
updateSubscription(id: ID!): Subscription! @hasRole(role: ADMIN)
|
||||||
|
|
||||||
|
# updateSubscriptionLink is to update the subscription link without re-fetching nodes.
|
||||||
|
diff --git a/graphql/service/config/mutation_utils.go b/graphql/service/config/mutation_utils.go
|
||||||
|
index fd61554..feb94d3 100644
|
||||||
|
--- a/graphql/service/config/mutation_utils.go
|
||||||
|
+++ b/graphql/service/config/mutation_utils.go
|
||||||
|
@@ -21,6 +21,7 @@ import (
|
||||||
|
"github.com/daeuniverse/dae-wing/dae"
|
||||||
|
"github.com/daeuniverse/dae-wing/db"
|
||||||
|
"github.com/daeuniverse/dae-wing/graphql/service/config/global"
|
||||||
|
+ "github.com/daeuniverse/dae-wing/graphql/service/general"
|
||||||
|
daeConfig "github.com/daeuniverse/dae/config"
|
||||||
|
"github.com/daeuniverse/dae/pkg/config_parser"
|
||||||
|
"github.com/graph-gophers/graphql-go"
|
||||||
|
@@ -269,21 +270,69 @@ func restartAfterReloadTimeout() {
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
-func Run(d *gorm.DB, noLoad bool) (n int32, err error) {
|
||||||
|
+func runTransaction(ctx context.Context, noLoad bool) (n int32, err error) {
|
||||||
|
+ tx := db.BeginTx(ctx)
|
||||||
|
+ if tx.Error != nil {
|
||||||
|
+ return 0, tx.Error
|
||||||
|
+ }
|
||||||
|
+ n, err = runLocked(tx, noLoad)
|
||||||
|
+ if err != nil {
|
||||||
|
+ tx.Rollback()
|
||||||
|
+ return 0, err
|
||||||
|
+ }
|
||||||
|
+ if err = tx.Commit().Error; err != nil {
|
||||||
|
+ return 0, err
|
||||||
|
+ }
|
||||||
|
+ return n, nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func Run(ctx context.Context, noLoad bool) (n int32, err error) {
|
||||||
|
if ok := runLock.TryLock(); !ok {
|
||||||
|
return 0, fmt.Errorf("the last request didn't complete; make a cup of tea and take a break")
|
||||||
|
}
|
||||||
|
defer runLock.Unlock()
|
||||||
|
+ return runTransaction(ctx, noLoad)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func ApplyIfRunning(ctx context.Context) error {
|
||||||
|
+ runLock.Lock()
|
||||||
|
+ defer runLock.Unlock()
|
||||||
|
+
|
||||||
|
+ daeResolver := general.DaeResolver{Ctx: ctx}
|
||||||
|
+ running, err := daeResolver.Running()
|
||||||
|
+ if err != nil || !running {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ modified, err := daeResolver.Modified()
|
||||||
|
+ if err != nil || !modified {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ _, err = runTransaction(ctx, false)
|
||||||
|
+ return err
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func runLocked(d *gorm.DB, noLoad bool) (n int32, err error) {
|
||||||
|
//// Dry run.
|
||||||
|
if noLoad {
|
||||||
|
ch := make(chan error)
|
||||||
|
- dae.ChReloadConfigs <- &dae.ReloadMessage{
|
||||||
|
+ reloadMsg := &dae.ReloadMessage{
|
||||||
|
Config: dae.EmptyConfig,
|
||||||
|
Callback: ch,
|
||||||
|
}
|
||||||
|
- err = <-ch
|
||||||
|
- if err != nil {
|
||||||
|
- return 0, fmt.Errorf("failed to dryrun: %w; see more in log and report bugs", err)
|
||||||
|
+ select {
|
||||||
|
+ case dae.ChReloadConfigs <- reloadMsg:
|
||||||
|
+ case <-time.After(reloadWaitTimeout):
|
||||||
|
+ restartAfterReloadTimeout()
|
||||||
|
+ return 0, fmt.Errorf("failed to submit dryrun within %s; restarting daed to recover runtime", reloadWaitTimeout)
|
||||||
|
+ }
|
||||||
|
+ select {
|
||||||
|
+ case err = <-ch:
|
||||||
|
+ if err != nil {
|
||||||
|
+ return 0, fmt.Errorf("failed to dryrun: %w; see more in log and report bugs", err)
|
||||||
|
+ }
|
||||||
|
+ case <-time.After(reloadWaitTimeout):
|
||||||
|
+ restartAfterReloadTimeout()
|
||||||
|
+ return 0, fmt.Errorf("dryrun timed out after %s; restarting daed to recover runtime", reloadWaitTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Running -> false
|
||||||
|
diff --git a/graphql/service/subscription/mutation_utils.go b/graphql/service/subscription/mutation_utils.go
|
||||||
|
index e55e65b..e01a342 100644
|
||||||
|
--- a/graphql/service/subscription/mutation_utils.go
|
||||||
|
+++ b/graphql/service/subscription/mutation_utils.go
|
||||||
|
@@ -18,6 +18,7 @@ import (
|
||||||
|
"github.com/daeuniverse/dae-wing/dae"
|
||||||
|
"github.com/daeuniverse/dae-wing/db"
|
||||||
|
"github.com/daeuniverse/dae-wing/graphql/internal"
|
||||||
|
+ "github.com/daeuniverse/dae-wing/graphql/service/config"
|
||||||
|
"github.com/daeuniverse/dae-wing/graphql/service/node"
|
||||||
|
"github.com/daeuniverse/dae/common/subscription"
|
||||||
|
"github.com/go-co-op/gocron"
|
||||||
|
@@ -171,8 +172,9 @@ func AutoUpdateVersionByIds(d *gorm.DB, ids []uint) (err error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
- schedulerCache = make(map[uint]*gocron.Scheduler)
|
||||||
|
- schedulerMu sync.RWMutex
|
||||||
|
+ schedulerCache = make(map[uint]*gocron.Scheduler)
|
||||||
|
+ schedulerMu sync.RWMutex
|
||||||
|
+ subscriptionUpdateMu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateAll(ctx context.Context) {
|
||||||
|
@@ -258,68 +260,161 @@ func Update(ctx context.Context, _id graphql.ID) (r *Resolver, err error) {
|
||||||
|
return &Resolver{Subscription: m}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-func UpdateById(ctx context.Context, subId uint) (sub *db.Subscription, err error) {
|
||||||
|
- // Fetch node links.
|
||||||
|
- var m db.Subscription
|
||||||
|
- if err = db.DB(ctx).Where(&db.Subscription{ID: subId}).First(&m).Error; err != nil {
|
||||||
|
- return nil, err
|
||||||
|
+func reconcileSubscriptionNodes(tx *gorm.DB, subId uint, links []string) error {
|
||||||
|
+ var existing []db.Node
|
||||||
|
+ if err := tx.Where("subscription_id = ?", subId).Find(&existing).Error; err != nil {
|
||||||
|
+ return err
|
||||||
|
}
|
||||||
|
- links, err := fetchLinks(m.Link)
|
||||||
|
- if err != nil {
|
||||||
|
- return nil, err
|
||||||
|
+
|
||||||
|
+ incoming := make(map[string]struct{}, len(links))
|
||||||
|
+ for _, link := range links {
|
||||||
|
+ incoming[link] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
- tx := db.BeginTx(ctx)
|
||||||
|
- defer func() {
|
||||||
|
- if err == nil {
|
||||||
|
- tx.Commit()
|
||||||
|
+ current := make(map[string]struct{}, len(existing))
|
||||||
|
+ var staleIds []uint
|
||||||
|
+ for _, n := range existing {
|
||||||
|
+ if _, ok := incoming[n.Link]; ok {
|
||||||
|
+ current[n.Link] = struct{}{}
|
||||||
|
+ continue
|
||||||
|
+ }
|
||||||
|
+ staleIds = append(staleIds, n.ID)
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fixed := make(map[uint]struct{})
|
||||||
|
+ if len(staleIds) > 0 {
|
||||||
|
+ var fixedIds []uint
|
||||||
|
+ if err := tx.Table("group_nodes").
|
||||||
|
+ Distinct("group_nodes.node_id").
|
||||||
|
+ Joins("INNER JOIN groups ON groups.id = group_nodes.group_id").
|
||||||
|
+ Where("groups.policy = ?", "fixed").
|
||||||
|
+ Where("group_nodes.node_id IN ?", staleIds).
|
||||||
|
+ Pluck("group_nodes.node_id", &fixedIds).Error; err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ for _, id := range fixedIds {
|
||||||
|
+ fixed[id] = struct{}{}
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ var detachIds, deleteIds []uint
|
||||||
|
+ for _, id := range staleIds {
|
||||||
|
+ if _, ok := fixed[id]; ok {
|
||||||
|
+ detachIds = append(detachIds, id)
|
||||||
|
} else {
|
||||||
|
- tx.Rollback()
|
||||||
|
+ deleteIds = append(deleteIds, id)
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if len(staleIds) > 0 {
|
||||||
|
+ if err := node.AutoUpdateVersionByIds(tx, staleIds); err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if len(detachIds) > 0 {
|
||||||
|
+ if err := tx.Model(&db.Node{}).
|
||||||
|
+ Where("id IN ?", detachIds).
|
||||||
|
+ Update("subscription_id", nil).Error; err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if len(deleteIds) > 0 {
|
||||||
|
+ if err := tx.Exec("DELETE FROM group_nodes WHERE node_id IN ?", deleteIds).Error; err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ if err := tx.Where("id IN ?", deleteIds).Delete(&db.Node{}).Error; err != nil {
|
||||||
|
+ return err
|
||||||
|
}
|
||||||
|
- }()
|
||||||
|
- // Remove those nodes whose subscription are independent from any groups.
|
||||||
|
- subQuery := tx.Raw(`select nodes.id as id
|
||||||
|
- from nodes
|
||||||
|
- inner join group_nodes on group_nodes.node_id = nodes.id
|
||||||
|
- where subscription_id = ?`, subId)
|
||||||
|
-
|
||||||
|
- if err = tx.Where("subscription_id = ?", subId).
|
||||||
|
- Where("id not in (?)", subQuery).
|
||||||
|
- Select(clause.Associations).
|
||||||
|
- Delete(&db.Node{}).Error; err != nil {
|
||||||
|
- return nil, err
|
||||||
|
}
|
||||||
|
- // Import node links.
|
||||||
|
+
|
||||||
|
+ seen := make(map[string]struct{}, len(links))
|
||||||
|
var args []*internal.ImportArgument
|
||||||
|
for _, link := range links {
|
||||||
|
+ if _, ok := seen[link]; ok {
|
||||||
|
+ continue
|
||||||
|
+ }
|
||||||
|
+ seen[link] = struct{}{}
|
||||||
|
+ if _, ok := current[link]; ok {
|
||||||
|
+ continue
|
||||||
|
+ }
|
||||||
|
args = append(args, &internal.ImportArgument{Link: link})
|
||||||
|
}
|
||||||
|
- result, err := node.Import(tx, false, &subId, args)
|
||||||
|
+ result, err := node.Import(tx, true, &subId, args)
|
||||||
|
if err != nil {
|
||||||
|
- return nil, err
|
||||||
|
+ return errors.New("failed to import subscription node")
|
||||||
|
}
|
||||||
|
- hasAnyCandidate := false
|
||||||
|
for _, r := range result {
|
||||||
|
- if r.Error == nil {
|
||||||
|
- hasAnyCandidate = true
|
||||||
|
- break
|
||||||
|
+ if r.Error != nil {
|
||||||
|
+ return errors.New("failed to import subscription node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- if !hasAnyCandidate {
|
||||||
|
- return nil, fmt.Errorf("interrupt to update subscription: no any valid node can be imported")
|
||||||
|
+
|
||||||
|
+ var count int64
|
||||||
|
+ if err := tx.Model(&db.Node{}).Where("subscription_id = ?", subId).Count(&count).Error; err != nil {
|
||||||
|
+ return err
|
||||||
|
}
|
||||||
|
- // Update updated_at and return the latest version.
|
||||||
|
- if err = tx.Model(&m).
|
||||||
|
+ if count == 0 {
|
||||||
|
+ return fmt.Errorf("interrupt to update subscription: no any valid node can be imported")
|
||||||
|
+ }
|
||||||
|
+ return nil
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func updateSubscriptionTx(ctx context.Context, m *db.Subscription, subId uint, links []string) (err error) {
|
||||||
|
+ subscriptionUpdateMu.Lock()
|
||||||
|
+ defer subscriptionUpdateMu.Unlock()
|
||||||
|
+
|
||||||
|
+ tx := db.BeginTx(ctx)
|
||||||
|
+ if tx.Error != nil {
|
||||||
|
+ return tx.Error
|
||||||
|
+ }
|
||||||
|
+ defer func() {
|
||||||
|
+ if err != nil {
|
||||||
|
+ tx.Rollback()
|
||||||
|
+ }
|
||||||
|
+ }()
|
||||||
|
+ var current db.Subscription
|
||||||
|
+ if err = tx.Where(&db.Subscription{ID: subId}).First(¤t).Error; err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ if current.Link != m.Link {
|
||||||
|
+ return fmt.Errorf("subscription changed during update; retry")
|
||||||
|
+ }
|
||||||
|
+ *m = current
|
||||||
|
+
|
||||||
|
+ if err = reconcileSubscriptionNodes(tx, subId, links); err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ q := tx.Model(m).
|
||||||
|
Clauses(clause.Returning{}).
|
||||||
|
Where(&db.Subscription{ID: subId}).
|
||||||
|
- Update("updated_at", time.Now()).Error; err != nil {
|
||||||
|
- return nil, err
|
||||||
|
+ Update("updated_at", time.Now())
|
||||||
|
+ if q.Error != nil {
|
||||||
|
+ return q.Error
|
||||||
|
+ }
|
||||||
|
+ if q.RowsAffected == 0 {
|
||||||
|
+ return fmt.Errorf("no such subscription")
|
||||||
|
}
|
||||||
|
-
|
||||||
|
- // Update modified if subscription is referenced by running config.
|
||||||
|
if err = AutoUpdateVersionByIds(tx, []uint{subId}); err != nil {
|
||||||
|
+ return err
|
||||||
|
+ }
|
||||||
|
+ return tx.Commit().Error
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func UpdateById(ctx context.Context, subId uint) (sub *db.Subscription, err error) {
|
||||||
|
+ var m db.Subscription
|
||||||
|
+ if err = db.DB(ctx).Where(&db.Subscription{ID: subId}).First(&m).Error; err != nil {
|
||||||
|
+ return nil, err
|
||||||
|
+ }
|
||||||
|
+ links, err := fetchLinks(m.Link)
|
||||||
|
+ if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
+ if err = updateSubscriptionTx(ctx, &m, subId, links); err != nil {
|
||||||
|
+ return nil, err
|
||||||
|
+ }
|
||||||
|
+ if err = config.ApplyIfRunning(ctx); err != nil {
|
||||||
|
+ return nil, fmt.Errorf("subscription updated but failed to apply runtime: %w", err)
|
||||||
|
+ }
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -399,6 +494,8 @@ func UpdateLink(ctx context.Context, _id graphql.ID, link string) (r *Resolver,
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
+ subscriptionUpdateMu.Lock()
|
||||||
|
+ defer subscriptionUpdateMu.Unlock()
|
||||||
|
|
||||||
|
tx := db.BeginTx(ctx)
|
||||||
|
defer func() {
|
||||||
|
diff --git a/graphql/service/subscription/mutation_utils_test.go b/graphql/service/subscription/mutation_utils_test.go
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..18ff9b9
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/graphql/service/subscription/mutation_utils_test.go
|
||||||
|
@@ -0,0 +1,212 @@
|
||||||
|
+/*
|
||||||
|
+ * SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
+ * Copyright (c) 2023, daeuniverse Organization <team@v2raya.org>
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+package subscription
|
||||||
|
+
|
||||||
|
+import (
|
||||||
|
+ "context"
|
||||||
|
+ "fmt"
|
||||||
|
+ "testing"
|
||||||
|
+ "time"
|
||||||
|
+
|
||||||
|
+ "github.com/daeuniverse/dae-wing/db"
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
+func mustNoError(t *testing.T, err error) {
|
||||||
|
+ t.Helper()
|
||||||
|
+ if err != nil {
|
||||||
|
+ t.Fatal(err)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func assertError(t *testing.T, err error) {
|
||||||
|
+ t.Helper()
|
||||||
|
+ if err == nil {
|
||||||
|
+ t.Fatal("expected error")
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func assertEqualValues(t *testing.T, want, got interface{}) {
|
||||||
|
+ t.Helper()
|
||||||
|
+ if fmt.Sprint(want) != fmt.Sprint(got) {
|
||||||
|
+ t.Fatalf("want %v, got %v", want, got)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func assertNotNil(t *testing.T, value *uint) {
|
||||||
|
+ t.Helper()
|
||||||
|
+ if value == nil {
|
||||||
|
+ t.Fatal("expected non-nil value")
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func assertNil(t *testing.T, value *uint) {
|
||||||
|
+ t.Helper()
|
||||||
|
+ if value != nil {
|
||||||
|
+ t.Fatalf("expected nil, got %v", *value)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func assertZero(t *testing.T, value int64) {
|
||||||
|
+ t.Helper()
|
||||||
|
+ if value != 0 {
|
||||||
|
+ t.Fatalf("expected zero, got %d", value)
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func initReconcileTestDB(t *testing.T) context.Context {
|
||||||
|
+ t.Helper()
|
||||||
|
+ mustNoError(t, db.InitDatabase(t.TempDir()))
|
||||||
|
+ return context.Background()
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func createTestSubscription(t *testing.T, ctx context.Context, links ...string) (db.Subscription, []db.Node) {
|
||||||
|
+ t.Helper()
|
||||||
|
+ sub := db.Subscription{
|
||||||
|
+ UpdatedAt: time.Now(),
|
||||||
|
+ Link: "https://example.invalid/subscription",
|
||||||
|
+ Status: "",
|
||||||
|
+ Info: "",
|
||||||
|
+ }
|
||||||
|
+ mustNoError(t, db.DB(ctx).Create(&sub).Error)
|
||||||
|
+
|
||||||
|
+ nodes := make([]db.Node, 0, len(links))
|
||||||
|
+ for i, link := range links {
|
||||||
|
+ node := db.Node{
|
||||||
|
+ Link: link,
|
||||||
|
+ Name: "node",
|
||||||
|
+ Address: "192.0.2.1",
|
||||||
|
+ Protocol: "ss",
|
||||||
|
+ SubscriptionID: &sub.ID,
|
||||||
|
+ }
|
||||||
|
+ node.Name += string(rune('A' + i))
|
||||||
|
+ mustNoError(t, db.DB(ctx).Create(&node).Error)
|
||||||
|
+ nodes = append(nodes, node)
|
||||||
|
+ }
|
||||||
|
+ return sub, nodes
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func createTestGroup(t *testing.T, ctx context.Context, policy string, systemID *uint, nodes ...db.Node) db.Group {
|
||||||
|
+ t.Helper()
|
||||||
|
+ group := db.Group{Name: policy + time.Now().Format("150405.000000000"), Policy: policy, SystemID: systemID}
|
||||||
|
+ mustNoError(t, db.DB(ctx).Create(&group).Error)
|
||||||
|
+ mustNoError(t, db.DB(ctx).Model(&group).Association("Node").Append(nodes))
|
||||||
|
+ return group
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func countGroupNode(t *testing.T, ctx context.Context, groupID, nodeID uint) int64 {
|
||||||
|
+ t.Helper()
|
||||||
|
+ var count int64
|
||||||
|
+ mustNoError(t, db.DB(ctx).Table("group_nodes").
|
||||||
|
+ Where("group_id = ? AND node_id = ?", groupID, nodeID).
|
||||||
|
+ Count(&count).Error)
|
||||||
|
+ return count
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func TestReconcileKeepsCurrentReferencedNode(t *testing.T) {
|
||||||
|
+ ctx := initReconcileTestDB(t)
|
||||||
|
+ sub, nodes := createTestSubscription(t, ctx, "ss://current")
|
||||||
|
+ group := createTestGroup(t, ctx, "min_moving_avg", nil, nodes[0])
|
||||||
|
+
|
||||||
|
+ mustNoError(t, reconcileSubscriptionNodes(db.DB(ctx), sub.ID, []string{"ss://current"}))
|
||||||
|
+
|
||||||
|
+ var got db.Node
|
||||||
|
+ mustNoError(t, db.DB(ctx).First(&got, nodes[0].ID).Error)
|
||||||
|
+ assertNotNil(t, got.SubscriptionID)
|
||||||
|
+ assertEqualValues(t, sub.ID, *got.SubscriptionID)
|
||||||
|
+ assertEqualValues(t, 1, countGroupNode(t, ctx, group.ID, got.ID))
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func TestReconcileDeletesStaleUnreferencedNode(t *testing.T) {
|
||||||
|
+ ctx := initReconcileTestDB(t)
|
||||||
|
+ sub, nodes := createTestSubscription(t, ctx, "ss://current", "ss://stale")
|
||||||
|
+
|
||||||
|
+ mustNoError(t, reconcileSubscriptionNodes(db.DB(ctx), sub.ID, []string{"ss://current"}))
|
||||||
|
+
|
||||||
|
+ var count int64
|
||||||
|
+ mustNoError(t, db.DB(ctx).Model(&db.Node{}).Where("id = ?", nodes[1].ID).Count(&count).Error)
|
||||||
|
+ assertZero(t, count)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func TestReconcileDeletesStaleNormalGroupNode(t *testing.T) {
|
||||||
|
+ ctx := initReconcileTestDB(t)
|
||||||
|
+ system := db.System{Running: true}
|
||||||
|
+ mustNoError(t, db.DB(ctx).Create(&system).Error)
|
||||||
|
+ sub, nodes := createTestSubscription(t, ctx, "ss://current", "ss://stale")
|
||||||
|
+ group := createTestGroup(t, ctx, "min_moving_avg", &system.ID, nodes...)
|
||||||
|
+
|
||||||
|
+ mustNoError(t, reconcileSubscriptionNodes(db.DB(ctx), sub.ID, []string{"ss://current"}))
|
||||||
|
+
|
||||||
|
+ var gotGroup db.Group
|
||||||
|
+ mustNoError(t, db.DB(ctx).First(&gotGroup, group.ID).Error)
|
||||||
|
+ assertEqualValues(t, 1, gotGroup.Version)
|
||||||
|
+ assertEqualValues(t, 0, countGroupNode(t, ctx, group.ID, nodes[1].ID))
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func TestReconcileDetachesStaleFixedNode(t *testing.T) {
|
||||||
|
+ ctx := initReconcileTestDB(t)
|
||||||
|
+ sub, nodes := createTestSubscription(t, ctx, "ss://current", "ss://fixed-stale")
|
||||||
|
+ group := createTestGroup(t, ctx, "fixed", nil, nodes[1])
|
||||||
|
+
|
||||||
|
+ mustNoError(t, reconcileSubscriptionNodes(db.DB(ctx), sub.ID, []string{"ss://current"}))
|
||||||
|
+
|
||||||
|
+ var got db.Node
|
||||||
|
+ mustNoError(t, db.DB(ctx).First(&got, nodes[1].ID).Error)
|
||||||
|
+ assertNil(t, got.SubscriptionID)
|
||||||
|
+ assertEqualValues(t, 1, countGroupNode(t, ctx, group.ID, got.ID))
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func TestReconcilePreservesNodeReferencedByFixedAndNormalGroups(t *testing.T) {
|
||||||
|
+ ctx := initReconcileTestDB(t)
|
||||||
|
+ sub, nodes := createTestSubscription(t, ctx, "ss://current", "ss://shared-stale")
|
||||||
|
+ fixed := createTestGroup(t, ctx, "fixed", nil, nodes[1])
|
||||||
|
+ normal := createTestGroup(t, ctx, "min", nil, nodes[1])
|
||||||
|
+
|
||||||
|
+ mustNoError(t, reconcileSubscriptionNodes(db.DB(ctx), sub.ID, []string{"ss://current"}))
|
||||||
|
+
|
||||||
|
+ var got db.Node
|
||||||
|
+ mustNoError(t, db.DB(ctx).First(&got, nodes[1].ID).Error)
|
||||||
|
+ assertNil(t, got.SubscriptionID)
|
||||||
|
+ assertEqualValues(t, 1, countGroupNode(t, ctx, fixed.ID, got.ID))
|
||||||
|
+ assertEqualValues(t, 1, countGroupNode(t, ctx, normal.ID, got.ID))
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func TestReconcileAllLinksAlreadyExistSucceeds(t *testing.T) {
|
||||||
|
+ ctx := initReconcileTestDB(t)
|
||||||
|
+ sub, nodes := createTestSubscription(t, ctx, "ss://one", "ss://two")
|
||||||
|
+
|
||||||
|
+ mustNoError(t, reconcileSubscriptionNodes(db.DB(ctx), sub.ID, []string{"ss://one", "ss://two", "ss://two"}))
|
||||||
|
+
|
||||||
|
+ var count int64
|
||||||
|
+ mustNoError(t, db.DB(ctx).Model(&db.Node{}).Where("subscription_id = ?", sub.ID).Count(&count).Error)
|
||||||
|
+ assertEqualValues(t, len(nodes), count)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func TestReconcileRollsBackWhenAnyIncomingNodeIsInvalid(t *testing.T) {
|
||||||
|
+ ctx := initReconcileTestDB(t)
|
||||||
|
+ sub, nodes := createTestSubscription(t, ctx, "ss://current", "ss://stale")
|
||||||
|
+ tx := db.BeginTx(ctx)
|
||||||
|
+
|
||||||
|
+ assertError(t, reconcileSubscriptionNodes(tx, sub.ID, []string{"ss://current", "not-a-node-link"}))
|
||||||
|
+ mustNoError(t, tx.Rollback().Error)
|
||||||
|
+
|
||||||
|
+ var count int64
|
||||||
|
+ mustNoError(t, db.DB(ctx).Model(&db.Node{}).Where("id IN ?", []uint{nodes[0].ID, nodes[1].ID}).Count(&count).Error)
|
||||||
|
+ assertEqualValues(t, 2, count)
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+func TestUpdateSubscriptionRejectsChangedLink(t *testing.T) {
|
||||||
|
+ ctx := initReconcileTestDB(t)
|
||||||
|
+ sub, nodes := createTestSubscription(t, ctx, "ss://current")
|
||||||
|
+ snapshot := sub
|
||||||
|
+ mustNoError(t, db.DB(ctx).Model(&sub).Update("link", "https://example.invalid/changed").Error)
|
||||||
|
+
|
||||||
|
+ assertError(t, updateSubscriptionTx(ctx, &snapshot, sub.ID, []string{"ss://replacement"}))
|
||||||
|
+
|
||||||
|
+ var got db.Node
|
||||||
|
+ mustNoError(t, db.DB(ctx).First(&got, nodes[0].ID).Error)
|
||||||
|
+ assertNotNil(t, got.SubscriptionID)
|
||||||
|
+ assertEqualValues(t, sub.ID, *got.SubscriptionID)
|
||||||
|
+}
|
||||||
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-daede
|
PKG_NAME:=luci-app-daede
|
||||||
PKG_VERSION:=1.14.7
|
PKG_VERSION:=1.14.7
|
||||||
PKG_RELEASE:=34
|
PKG_RELEASE:=35
|
||||||
PKG_MAINTAINER:=kenzok8
|
PKG_MAINTAINER:=kenzok8
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||||
|
|
||||||
|
|||||||
@@ -121,12 +121,27 @@ if [ "$fail" -ne 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$count" -gt 0 ] && [ "$ok" -gt 0 ] && /bin/pidof daed >/dev/null 2>&1; then
|
if [ "$count" -gt 0 ] && [ "$ok" -gt 0 ] && /bin/pidof daed >/dev/null 2>&1; then
|
||||||
|
state_body="$TMPDIR/state.json"
|
||||||
|
state_resp="$TMPDIR/state.out"
|
||||||
|
printf '%s' '{"query":"query General{general{dae{running modified}}}","variables":{}}' > "$state_body"
|
||||||
|
if ! post_graphql "$state_body" "$state_resp" "$token" || grep -q '"errors"' "$state_resp"; then
|
||||||
|
fail 8 "failed to query daed runtime state: $(cat "$state_resp" 2>/dev/null)"
|
||||||
|
fi
|
||||||
|
if ! grep -q '"running"[[:space:]]*:[[:space:]]*true' "$state_resp"; then
|
||||||
|
log "daed proxy is stopped; skipping runtime apply"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if ! grep -q '"modified"[[:space:]]*:[[:space:]]*true' "$state_resp"; then
|
||||||
|
log "daed runtime is already up to date"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
run_body="$TMPDIR/run.json"
|
run_body="$TMPDIR/run.json"
|
||||||
run_resp="$TMPDIR/run.out"
|
run_resp="$TMPDIR/run.out"
|
||||||
printf '{"query":"mutation Run($dry:Boolean!){run(dry:$dry)}","variables":{"dry":false}}' > "$run_body"
|
printf '{"query":"mutation Run($dry:Boolean!){run(dry:$dry)}","variables":{"dry":false}}' > "$run_body"
|
||||||
log "applying updated subscriptions"
|
log "applying updated subscriptions"
|
||||||
if ! post_graphql "$run_body" "$run_resp" "$token" || grep -q '"errors"' "$run_resp"; then
|
if ! post_graphql "$run_body" "$run_resp" "$token" || grep -q '"errors"' "$run_resp"; then
|
||||||
fail 8 "failed to apply updated subscriptions: $(cat "$run_resp" 2>/dev/null)"
|
fail 9 "failed to apply updated subscriptions: $(cat "$run_resp" 2>/dev/null)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.7.16
|
PKG_VERSION:=26.7.16
|
||||||
PKG_RELEASE:=188
|
PKG_RELEASE:=189
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -24,23 +24,26 @@ o:value("https://cdn.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/
|
|||||||
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
|
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
|
||||||
o:value("https://cdn.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt", translate("Loukky/gfwlist-by-loukky"))
|
o:value("https://cdn.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt", translate("Loukky/gfwlist-by-loukky"))
|
||||||
o:value("https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt", translate("gfwlist/gfwlist"))
|
o:value("https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt", translate("gfwlist/gfwlist"))
|
||||||
|
o:value("https://cdn.jsdelivr.net/gh/pexcn/daily@gh-pages/gfwlist/gfwlist.txt", translate("pexcn/gfwlist"))
|
||||||
o.default = o.keylist[2]
|
o.default = o.keylist[2]
|
||||||
|
|
||||||
----chnroute URL
|
----chnroute URL
|
||||||
o = s:option(DynamicList, "chnroute_url", translate("China IPs(chnroute) Update URL"))
|
o = s:option(DynamicList, "chnroute_url", translate("China IPs(chnroute) Update URL"))
|
||||||
o:depends("geo2rule", false)
|
o:depends("geo2rule", false)
|
||||||
o:value("https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("gaoyifan/china-operator-ip/china"))
|
|
||||||
o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang.CN"))
|
o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang.CN"))
|
||||||
|
o:value("https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("gaoyifan/china-operator-ip/china"))
|
||||||
o:value("https://cdn.jsdelivr.net/gh/soffchen/GeoIP2-CN@release/CN-ip-cidr.txt", translate("soffchen/GeoIP2-CN"))
|
o:value("https://cdn.jsdelivr.net/gh/soffchen/GeoIP2-CN@release/CN-ip-cidr.txt", translate("soffchen/GeoIP2-CN"))
|
||||||
o:value("https://cdn.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us/GeoIP2-CN"))
|
o:value("https://cdn.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us/GeoIP2-CN"))
|
||||||
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_IP_No_IPv6.txt", translate("ios_rule_script/ChinaMax_IP_No_IPv6"))
|
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_IP_No_IPv6.txt", translate("ios_rule_script/ChinaMax_IP_No_IPv6"))
|
||||||
|
o:value("https://cdn.jsdelivr.net/gh/pexcn/daily@gh-pages/chnroute/chnroute.txt", translate("pexcn/chnroute"))
|
||||||
|
|
||||||
----chnroute6 URL
|
----chnroute6 URL
|
||||||
o = s:option(DynamicList, "chnroute6_url", translate("China IPv6s(chnroute6) Update URL"))
|
o = s:option(DynamicList, "chnroute6_url", translate("China IPv6s(chnroute6) Update URL"))
|
||||||
o:depends("geo2rule", false)
|
o:depends("geo2rule", false)
|
||||||
o:value("https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt", translate("gaoyifan/china-operator-ip/china6"))
|
|
||||||
o:value("https://ispip.clang.cn/all_cn_ipv6.txt", translate("Clang.CN.IPv6"))
|
o:value("https://ispip.clang.cn/all_cn_ipv6.txt", translate("Clang.CN.IPv6"))
|
||||||
|
o:value("https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt", translate("gaoyifan/china-operator-ip/china6"))
|
||||||
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_IP.txt", translate("ios_rule_script/ChinaMax_IP"))
|
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_IP.txt", translate("ios_rule_script/ChinaMax_IP"))
|
||||||
|
o:value("https://cdn.jsdelivr.net/gh/pexcn/daily@gh-pages/chnroute/chnroute6.txt", translate("pexcn/chnroute6"))
|
||||||
|
|
||||||
----chnlist URL
|
----chnlist URL
|
||||||
o = s:option(DynamicList, "chnlist_url", translate("China List(Chnlist) Update URL"))
|
o = s:option(DynamicList, "chnlist_url", translate("China List(Chnlist) Update URL"))
|
||||||
@@ -52,6 +55,7 @@ o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/china-
|
|||||||
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/apple-cn.txt", translate("Loyalsoldier/apple-cn"))
|
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/apple-cn.txt", translate("Loyalsoldier/apple-cn"))
|
||||||
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/google-cn.txt", translate("Loyalsoldier/google-cn"))
|
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/google-cn.txt", translate("Loyalsoldier/google-cn"))
|
||||||
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_Domain.txt", translate("ios_rule_script/ChinaMax_Domain"))
|
o:value("https://cdn.jsdelivr.net/gh/blackmatrix7/ios_rule_script@master/rule/Clash/ChinaMax/ChinaMax_Domain.txt", translate("ios_rule_script/ChinaMax_Domain"))
|
||||||
|
o:value("https://cdn.jsdelivr.net/gh/pexcn/daily@gh-pages/chinalist/chinalist.txt", translate("pexcn/chinalist"))
|
||||||
|
|
||||||
if has_xray or has_singbox then
|
if has_xray or has_singbox then
|
||||||
o = s:option(Value, "geoip_url", translate("GeoIP Update URL"))
|
o = s:option(Value, "geoip_url", translate("GeoIP Update URL"))
|
||||||
|
|||||||
@@ -154,8 +154,7 @@ function gen_outbound(flag, node, tag, proxy_table)
|
|||||||
MaxConcurrentTry = 4
|
MaxConcurrentTry = 4
|
||||||
} or nil
|
} or nil
|
||||||
},
|
},
|
||||||
network = (api.compare_versions(xray_version, "<", "26.7.11")) and node.transport or nil, -- Todo: Remove
|
[(api.compare_versions(xray_version, "<", "26.7.11")) and "network" or "method"] = node.transport, -- Todo: Remove version check and "network"
|
||||||
method = (api.compare_versions(xray_version, ">=", "26.7.11")) and node.transport or nil, -- Todo: Remove version check
|
|
||||||
security = node.stream_security,
|
security = node.stream_security,
|
||||||
tlsSettings = (node.stream_security == "tls") and {
|
tlsSettings = (node.stream_security == "tls") and {
|
||||||
serverName = node.tls_serverName,
|
serverName = node.tls_serverName,
|
||||||
@@ -646,8 +645,7 @@ function gen_config_server(node)
|
|||||||
protocol = node.protocol,
|
protocol = node.protocol,
|
||||||
settings = settings,
|
settings = settings,
|
||||||
streamSettings = {
|
streamSettings = {
|
||||||
network = (api.compare_versions(xray_version, "<", "26.7.11")) and node.transport or nil, -- Todo: Remove
|
[(api.compare_versions(xray_version, "<", "26.7.11")) and "network" or "method"] = node.transport, -- Todo: Remove version check and "network"
|
||||||
method = (api.compare_versions(xray_version, ">=", "26.7.11")) and node.transport or nil, -- Todo: Remove version check
|
|
||||||
security = "none",
|
security = "none",
|
||||||
tlsSettings = ("1" == node.tls) and {
|
tlsSettings = ("1" == node.tls) and {
|
||||||
disableSystemRoot = false,
|
disableSystemRoot = false,
|
||||||
|
|||||||
Reference in new issue
Block a user