From 35609b7b132158fc496b5c834766100e86f7c3bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:41:11 +0200 Subject: [PATCH 01/17] Bump github.com/Azure/go-ntlmssp (#4094) Bumps the go_modules group with 1 update in the / directory: [github.com/Azure/go-ntlmssp](https://github.com/Azure/go-ntlmssp). Updates `github.com/Azure/go-ntlmssp` from 0.1.0 to 0.1.1 - [Release notes](https://github.com/Azure/go-ntlmssp/releases) - [Commits](https://github.com/Azure/go-ntlmssp/compare/v0.1.0...v0.1.1) --- updated-dependencies: - dependency-name: github.com/Azure/go-ntlmssp dependency-version: 0.1.1 dependency-type: indirect dependency-group: go_modules ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 331261f8..57f7155f 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( ) require ( - github.com/Azure/go-ntlmssp v0.1.0 // indirect + github.com/Azure/go-ntlmssp v0.1.1 // indirect github.com/andybalholm/brotli v1.2.1 // indirect github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22 // indirect github.com/bytedance/gopkg v0.1.4 // indirect diff --git a/go.sum b/go.sum index 45931b83..78341c48 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A= -github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= +github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw= +github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= From a62c637632d0e4f0ff74fe185a3ea50048cb73d8 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 26 Apr 2026 17:34:31 +0200 Subject: [PATCH 02/17] DNS outbound: Add rules --- web/assets/js/model/outbound.js | 143 ++++++++++++++++++++++++++++++-- web/html/form/outbound.html | 79 ++++++++++++++---- web/service/inbound.go | 2 +- web/web.go | 9 +- 4 files changed, 209 insertions(+), 24 deletions(-) diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js index 97602815..8db9d8e2 100644 --- a/web/assets/js/model/outbound.js +++ b/web/assets/js/model/outbound.js @@ -97,6 +97,74 @@ const Address_Port_Strategy = { TxtPortAndAddress: "txtportandaddress" }; +const DNSRuleActions = ['direct', 'drop', 'reject', 'hijack']; + +function normalizeDNSRuleField(value) { + if (value === null || value === undefined) { + return ''; + } + if (Array.isArray(value)) { + return value.map(item => item.toString().trim()).filter(item => item.length > 0).join(','); + } + return value.toString().trim(); +} + +function normalizeDNSRuleAction(action) { + action = ObjectUtil.isEmpty(action) ? 'direct' : action.toString().toLowerCase().trim(); + return DNSRuleActions.includes(action) ? action : 'direct'; +} + +function parseLegacyDNSBlockTypes(blockTypes) { + if (blockTypes === null || blockTypes === undefined || blockTypes === '') { + return []; + } + + if (Array.isArray(blockTypes)) { + return blockTypes + .map(item => Number(item)) + .filter(item => Number.isInteger(item) && item >= 0 && item <= 65535); + } + + if (typeof blockTypes === 'number') { + return Number.isInteger(blockTypes) && blockTypes >= 0 && blockTypes <= 65535 ? [blockTypes] : []; + } + + return blockTypes + .toString() + .split(',') + .map(item => item.trim()) + .filter(item => /^\d+$/.test(item)) + .map(item => Number(item)) + .filter(item => item >= 0 && item <= 65535); +} + +function buildLegacyDNSRules(nonIPQuery, blockTypes) { + const mode = ['reject', 'drop', 'skip'].includes(nonIPQuery) ? nonIPQuery : 'reject'; + const rules = []; + const parsedBlockTypes = parseLegacyDNSBlockTypes(blockTypes); + + if (parsedBlockTypes.length > 0) { + rules.push(new Outbound.DNSRule(mode === 'reject' ? 'reject' : 'drop', parsedBlockTypes.join(','))); + } + + rules.push(new Outbound.DNSRule('hijack', '1,28')); + rules.push(new Outbound.DNSRule(mode === 'skip' ? 'direct' : mode)); + + return rules; +} + +function getDNSRulesFromJson(json = {}) { + if (Array.isArray(json.rules) && json.rules.length > 0) { + return json.rules.map(rule => Outbound.DNSRule.fromJson(rule)); + } + + if (json.nonIPQuery !== undefined || json.blockTypes !== undefined) { + return buildLegacyDNSRules(json.nonIPQuery, json.blockTypes); + } + + return []; +} + Object.freeze(Protocols); Object.freeze(SSMethods); Object.freeze(TLS_FLOW_CONTROL); @@ -107,6 +175,7 @@ Object.freeze(WireguardDomainStrategy); Object.freeze(USERS_SECURITY); Object.freeze(MODE_OPTION); Object.freeze(Address_Port_Strategy); +Object.freeze(DNSRuleActions); class CommonClass { @@ -1277,20 +1346,69 @@ Outbound.BlackholeSettings = class extends CommonClass { }; } }; + +Outbound.DNSRule = class extends CommonClass { + constructor(action = 'direct', qtype = '', domain = '') { + super(); + this.action = action; + this.qtype = qtype; + this.domain = domain; + } + + static fromJson(json = {}) { + return new Outbound.DNSRule( + json.action, + normalizeDNSRuleField(json.qtype), + normalizeDNSRuleField(json.domain), + ); + } + + toJson() { + const rule = { + action: normalizeDNSRuleAction(this.action), + }; + + const qtype = normalizeDNSRuleField(this.qtype); + if (!ObjectUtil.isEmpty(qtype)) { + if (/^\d+$/.test(qtype)) { + rule.qtype = Number(qtype); + } else { + rule.qtype = qtype; + } + } + + const domains = normalizeDNSRuleField(this.domain) + .split(',') + .map(d => d.trim()) + .filter(d => d.length > 0); + if (domains.length > 0) { + rule.domain = domains; + } + + return rule; + } +}; + Outbound.DNSSettings = class extends CommonClass { constructor( network = 'udp', address = '', port = 53, - nonIPQuery = 'reject', - blockTypes = [] + rules = [] ) { super(); this.network = network; this.address = address; this.port = port; - this.nonIPQuery = nonIPQuery; - this.blockTypes = blockTypes; + this.rules = Array.isArray(rules) ? rules.map(rule => rule instanceof Outbound.DNSRule ? rule : Outbound.DNSRule.fromJson(rule)) : []; + } + + addRule(action = 'direct') { + this.rules.push(new Outbound.DNSRule(action)); + } + + delRule(index) { + this.rules.splice(index, 1); } static fromJson(json = {}) { @@ -1298,10 +1416,23 @@ Outbound.DNSSettings = class extends CommonClass { json.network, json.address, json.port, - json.nonIPQuery, - json.blockTypes, + getDNSRulesFromJson(json), ); } + + toJson() { + const json = { + network: this.network, + address: this.address, + port: this.port, + }; + + if (this.rules.length > 0) { + json.rules = Outbound.DNSRule.toJsonArray(this.rules); + } + + return json; + } }; Outbound.VmessSettings = class extends CommonClass { constructor(address, port, id, security) { diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html index a9119cf0..c350d70f 100644 --- a/web/html/form/outbound.html +++ b/web/html/form/outbound.html @@ -190,22 +190,73 @@ > - - - [[ s ]] - + + - - - + + Rule [[ index + 1 ]] + + + + + + [[ action ]] + + + + + + + + + + + + + diff --git a/web/service/inbound.go b/web/service/inbound.go index 7d5d8932..8ab5e6a8 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -779,7 +779,7 @@ func (s *InboundService) writeBackClientSubID(sourceInboundID int, sourceProtoco } settingsBytes, err := json.Marshal(map[string][]model.Client{ - "clients": []model.Client{client}, + "clients": {client}, }) if err != nil { return false, err diff --git a/web/web.go b/web/web.go index 835e82e1..f25dada2 100644 --- a/web/web.go +++ b/web/web.go @@ -353,14 +353,17 @@ func (s *Server) startTask() { isTgbotenabled, err := s.settingService.GetTgbotEnabled() if (err == nil) && (isTgbotenabled) { runtime, err := s.settingService.GetTgbotRuntime() - if err != nil || runtime == "" { - logger.Errorf("Add NewStatsNotifyJob error[%s], Runtime[%s] invalid, will run default", err, runtime) + if err != nil { + logger.Warningf("Add NewStatsNotifyJob: failed to load runtime: %v; using default @daily", err) + runtime = "@daily" + } else if strings.TrimSpace(runtime) == "" { + logger.Warning("Add NewStatsNotifyJob runtime is empty, using default @daily") runtime = "@daily" } logger.Infof("Tg notify enabled,run at %s", runtime) _, err = s.cron.AddJob(runtime, job.NewStatsNotifyJob()) if err != nil { - logger.Warning("Add NewStatsNotifyJob error", err) + logger.Warningf("Add NewStatsNotifyJob: failed to schedule runtime %q: %v", runtime, err) return } From 4521beab7c245a3420ae558ce34c37fb9ed1a53d Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 26 Apr 2026 20:06:24 +0200 Subject: [PATCH 03/17] wireguard: link --- web/assets/js/model/inbound.js | 50 ++++++++++++++++++++++--- web/html/modals/inbound_info_modal.html | 15 +++++++- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js index 26da6cb3..e3816060 100644 --- a/web/assets/js/model/inbound.js +++ b/web/assets/js/model/inbound.js @@ -1907,7 +1907,7 @@ class Inbound extends XrayCommonClass { return url.toString(); } - getWireguardLink(address, port, remark, peerId) { + getWireguardTxt(address, port, remark, peerId) { let txt = `[Interface]\n` txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n` txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n` @@ -1929,6 +1929,48 @@ class Inbound extends XrayCommonClass { return txt; } + getWireguardLink(address, port, remark, peerId) { + const peer = this.settings?.peers?.[peerId]; + if (!peer) return ''; + + const link = `wireguard://${address}:${port}`; + const url = new URL(link); + url.username = peer.privateKey || ''; + + if (this.settings?.pubKey) { + url.searchParams.set("publickey", this.settings.pubKey); + } + if (Array.isArray(peer.allowedIPs) && peer.allowedIPs.length > 0 && peer.allowedIPs[0]) { + url.searchParams.set("address", peer.allowedIPs[0]); + } + if (this.settings?.mtu) { + url.searchParams.set("mtu", this.settings.mtu); + } + + url.hash = encodeURIComponent(remark); + return url.toString(); + } + + genWireguardLinks(remark = '', remarkModel = '-ieo') { + const addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname; + const separationChar = remarkModel.charAt(0); + let links = []; + this.settings.peers.forEach((p, index) => { + links.push(this.getWireguardLink(addr, this.port, remark + separationChar + (index + 1), index)); + }); + return links.join('\r\n'); + } + + genWireguardConfigs(remark = '', remarkModel = '-ieo') { + const addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname; + const separationChar = remarkModel.charAt(0); + let links = []; + this.settings.peers.forEach((p, index) => { + links.push(this.getWireguardTxt(addr, this.port, remark + separationChar + (index + 1), index)); + }); + return links.join('\r\n'); + } + genLink(address = '', port = this.port, forceTls = 'same', remark = '', client) { switch (this.protocol) { case Protocols.VMESS: @@ -1989,11 +2031,7 @@ class Inbound extends XrayCommonClass { } else { if (this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark); if (this.protocol == Protocols.WIREGUARD) { - let links = []; - this.settings.peers.forEach((p, index) => { - links.push(this.getWireguardLink(addr, this.port, remark + remarkModel.charAt(0) + (index + 1), index)); - }); - return links.join('\r\n'); + return this.genWireguardConfigs(remark, remarkModel); } return ''; } diff --git a/web/html/modals/inbound_info_modal.html b/web/html/modals/inbound_info_modal.html index 14b1d86b..67da21a9 100644 --- a/web/html/modals/inbound_info_modal.html +++ b/web/html/modals/inbound_info_modal.html @@ -513,9 +513,19 @@
+ Link + + Link + + + + + [[ infoModal.wireguardLinks[index] ]] + @@ -603,6 +613,7 @@ upStats: 0, downStats: 0, links: [], + wireguardLinks: [], index: null, isExpired: false, subLink: '', @@ -633,9 +644,11 @@ } } if (this.inbound.protocol == Protocols.WIREGUARD) { - this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n') + this.links = this.inbound.genWireguardConfigs(dbInbound.remark).split('\r\n') + this.wireguardLinks = this.inbound.genWireguardLinks(dbInbound.remark).split('\r\n') } else { this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings); + this.wireguardLinks = []; } if (this.clientSettings) { if (this.clientSettings.subId) { From 47e229e3231b5e74e03bcc758c9123d1637dbd82 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 26 Apr 2026 20:16:27 +0200 Subject: [PATCH 04/17] Default to dark theme when unset --- web/html/component/aThemeSwitch.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/html/component/aThemeSwitch.html b/web/html/component/aThemeSwitch.html index 431614d6..ca340da3 100644 --- a/web/html/component/aThemeSwitch.html +++ b/web/html/component/aThemeSwitch.html @@ -40,7 +40,8 @@ {{define "component/aThemeSwitch"}}