Compare commits

...

740 Commits

Author SHA1 Message Date
mhsanaei
01d4a7488d v2.8.5 2025-10-15 11:40:40 +02:00
mhsanaei
2b2ed3349a Xray-core v25.10.15 2025-10-15 11:40:04 +02:00
mhsanaei
d8523bbdac fix(import): prevent sqlite disk I/O error by validating temp DB then swapping 2025-10-14 22:03:17 +02:00
Slava M.
8afa39144e feat: add file logger support (#3575)
* feat: add backend for file logger
2025-10-09 17:39:29 +02:00
fpointsstar
00baeffe74 Update translate.ru_RU.toml (#3574)
Fix RU translation for login title: replace “Приветствие!” with “Добро пожаловать!” to match English “Welcome”.
2025-10-07 16:31:32 +02:00
mhsanaei
b578a33518 update dependencies 2025-10-07 13:49:08 +02:00
mhsanaei
8153e0ac05 fragment : MaxSplit 2025-10-07 13:46:30 +02:00
mhsanaei
2eb9d2e2e8 DevTools 2025-10-02 01:47:12 +02:00
Vadim Iskuchekov
a824875c4f fix: improve error handling in periodic traffic reset job (#3572) 2025-10-01 23:12:09 +02:00
JieXu
cafcb250ec Add support for OpenSUSE Leap (#3573)
* Update update.sh

* Update install.sh

* Update x-ui.sh

* Update x-ui.sh
2025-10-01 23:11:37 +02:00
mhsanaei
e7cfee570b first try native CPU implementation 2025-10-01 20:13:32 +02:00
JieXu
90c3529301 [Security] Replace timestamp-based password generation with random generator (#3571)
* Update x-ui.sh

* Update x-ui.sh

* Update x-ui.sh

* Update x-ui.sh
2025-10-01 18:37:31 +02:00
konstpic
b65ec83c39 fix: fix delete method (#3569)
Co-authored-by: Пичугин Константин <k.pichugin@comagic.dev>
2025-09-29 18:16:46 +02:00
konstpic
28a17a80ec feat: add ldap component (#3568)
* add ldap component

* fix: fix russian comments, tls cert verify default true

* feat: remove replaces go mod for local dev
2025-09-28 21:04:54 +02:00
Mikhail Grigorev
3056583388 feat: Add update script (#3555)
* feat: Add update script

* Small fix

* Fixed typo

* Fixed typo

* chmod +x

* Update x-ui

* Fixed update message

* Fixed typo

* Added downloading via IPv4

* Remove check_glibc_version

* Fixed self destroy

* Fixed typo

* Fixed self destroy

---------
2025-09-28 14:09:27 +02:00
Дмитрий Олегович Саенко
172f2ddaa7 fix russian translate in tgbot (#3557) 2025-09-25 15:21:40 +02:00
Tara Rostami
d69af328dc fix: login animation (#3559)
* Add IPv4 for wget in install

* fix: login animation
2025-09-25 15:16:50 +02:00
mhsanaei
ee0e3093ba Add IPv4 for wget in install 2025-09-25 15:08:13 +02:00
mhsanaei
89def9aee6 fix 2025-09-24 21:30:58 +02:00
mhsanaei
b2b0024648 login: autocomplete password 2025-09-24 20:41:32 +02:00
mhsanaei
5822758b7c tiny changes 2025-09-24 19:51:01 +02:00
mhsanaei
49430b3991 Update docker.yml 2025-09-24 15:42:01 +02:00
mhsanaei
104526aab2 v2.8.4 2025-09-24 11:47:43 +02:00
mhsanaei
a0c07241c0 minor changes 2025-09-24 11:47:14 +02:00
mhsanaei
adf3242602 bug fix 2025-09-24 11:44:02 +02:00
mhsanaei
3f62592e4b API improve security: returns 404 for unauthenticated API requests 2025-09-24 11:29:55 +02:00
Дмитрий Олегович Саенко
02bff4db6c max port to 65535 (#3536)
* add EXPOSE port in Dockerfile

* fix: max port 65 531 -> 65 535

* fix

---------

Co-authored-by: mhsanaei <ho3ein.sanaei@gmail.com>
2025-09-23 19:43:56 +02:00
Happ-dev
8ff4e1ff31 Add Happ client export open link (#3542)
Co-authored-by: y.sivushkin <y.sivushkin@corp.101xp.com>
2025-09-23 16:46:45 +02:00
mhsanaei
26c6438ec2 fix api : subid, uuid from inbound settings 2025-09-23 11:52:40 +02:00
Evgeny Volferts
b3e96230c4 Add Alpine Linux support (#3534)
* Add Alpine linux support

* Fix for reading logs
2025-09-22 21:56:43 +02:00
mhsanaei
1016f3b4f9 fix: outbound address for vless 2025-09-22 00:20:05 +02:00
mhsanaei
020bc9d77c v2.8.3 2025-09-21 21:20:45 +02:00
mhsanaei
5620d739c6 improved sub: BuildURLs 2025-09-21 21:20:37 +02:00
mhsanaei
d518979e4f pageSize to 25 2025-09-21 20:47:34 +02:00
mhsanaei
83f8a03b50 TGbot: improved (5x faster) 2025-09-21 19:27:05 +02:00
mhsanaei
b45e63a14a API: UUID for getClientTraffics 2025-09-21 19:16:54 +02:00
Дмитрий Олегович Саенко
3007bcff97 add EXPOSE port in Dockerfile (#3523) 2025-09-21 19:03:36 +02:00
mhsanaei
55f1d72af5 security fix: Uncontrolled data used in path expression 2025-09-21 18:51:54 +02:00
Sanaei
806ecbd7c5 Merge pull request #3528 from MHSanaei/security
Security issue fixed
2025-09-21 18:05:26 +02:00
mhsanaei
ae79b43cdb security fix: Use of insufficient randomness as the key of a cryptographic algorithm 2025-09-21 17:59:17 +02:00
mhsanaei
e64e6327ef security fix: Uncontrolled data used in path expression 2025-09-21 17:52:18 +02:00
mhsanaei
9f024b9e6a security fix: Workflow with permissions CWE-275 2025-09-21 17:47:16 +02:00
mhsanaei
eacfbc86b5 security fix: Command built from user-controlled sources CWE-78
https://cwe.mitre.org/data/definitions/78.html
https://owasp.org/www-community/attacks/Command_Injection
2025-09-21 17:39:30 +02:00
mhsanaei
37c17357fc undo vnext for vmess 2025-09-20 13:10:57 +02:00
mhsanaei
b35d339665 update dependencies 2025-09-20 09:48:54 +02:00
Tara Rostami
5e7a3db873 Minor Fixes (#3520) 2025-09-20 09:36:56 +02:00
mhsanaei
6ced549dea docs: add comments for all functions 2025-09-20 09:35:50 +02:00
mhsanaei
f60682a6b7 new: VACUUM database 2025-09-19 17:14:39 +02:00
mhsanaei
50bd7a8040 better design for dns presets 2025-09-19 15:44:00 +02:00
mhsanaei
7465768ff7 fix: subpath panic 2025-09-19 14:39:21 +02:00
mhsanaei
5b00a52c65 fix: ineffectual assignment to needRestart 2025-09-19 10:47:28 +02:00
mhsanaei
151f1173a1 Fix ineffassign “date” 2025-09-19 10:46:49 +02:00
mhsanaei
e262132b9d misspell 2025-09-19 10:35:03 +02:00
mhsanaei
ca0a7aeb5a readme: Go Report Card,Go Reference 2025-09-19 10:29:34 +02:00
mhsanaei
7447cec17e go package correction v2 2025-09-19 10:05:43 +02:00
mhsanaei
0ffd27c0aa v2.8.2 2025-09-19 00:22:15 +02:00
mhsanaei
054cb1dea0 go package correction 2025-09-18 23:12:14 +02:00
Drahonn
3757ae0b11 cpu history timeframe (#3509) 2025-09-18 20:52:31 +02:00
mhsanaei
e3883fca87 donate: nowpayments 2025-09-18 20:14:10 +02:00
mhsanaei
b46a0b404b enhancements 2025-09-18 16:28:09 +02:00
mhsanaei
0ce58a095a vscode: Debug for developer 2025-09-18 14:33:51 +02:00
mhsanaei
59ea2645db new: subJsonEnable
after this subEnable by default is true
and subJsonEnable is false
2025-09-18 13:56:04 +02:00
mhsanaei
8c8d280f14 minor change 2025-09-18 12:20:21 +02:00
Harry NG
c720008187 chore: update sub page URL (#3505)
* Fix: Shadowrocket link using base64 encoding

* chore: update url
2025-09-18 12:11:52 +02:00
mhsanaei
170d24499e fix PeriodicTrafficResetJob: log only when there are matching inbound 2025-09-18 11:41:11 +02:00
mhsanaei
99c79d4056 fix: online 2025-09-17 20:02:58 +02:00
RahGozar
fcdeb1fc79 feat: add UUID to ClientTraffic (#3491)
* Update client_traffic.go

* Update inbound.go
2025-09-17 17:45:28 +02:00
Harry NG
0a58b5e745 Fix: Shadowrocket link using base64 encoding (#3489) 2025-09-17 17:43:09 +02:00
Tara Rostami
db7e7dcd29 css [fixes] (#3487) 2025-09-17 15:47:04 +02:00
mhsanaei
01b8a27996 bug fix 2025-09-17 15:46:03 +02:00
mhsanaei
3764ece26c v2.8.1 2025-09-17 13:51:41 +02:00
Tara Rostami
d7efc2aef9 Minor Fixes (#3483)
* Minor Fixes

* Minor Fixes 2

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2025-09-17 13:47:01 +02:00
fgsfds
2eb8abf61e Improved xray logs display handling (#3475)
* improved xray logs handling

* fix download Xray Logs

* Update index.html
2025-09-17 13:19:55 +02:00
mhsanaei
299572a4c2 API: subid to getClientTraffics
/getClientTraffics/:email
/getClientTrafficsById/:id
2025-09-17 01:29:22 +02:00
mhsanaei
22afa50901 fix CPU History intervals 2025-09-17 01:08:59 +02:00
mhsanaei
bc274d1e1f Reality: placeholder for min, max 2025-09-16 18:57:27 +02:00
mhsanaei
dc21f41932 bug fix: del Depleted 2025-09-16 18:28:02 +02:00
mhsanaei
f137b1af76 bug fix: enable 2025-09-16 14:57:31 +02:00
mhsanaei
c4871ef8fe sub page: improved 2025-09-16 14:38:18 +02:00
mhsanaei
ecfffa882a CPU History, CPU Utilization 2025-09-16 14:15:18 +02:00
mhsanaei
3af5026abe tgbot: subscription, qrcode, link - for admin 2025-09-16 13:41:48 +02:00
mhsanaei
1de7accd7c vnext removed 2025-09-16 13:41:05 +02:00
Tara Rostami
76afff2a6f UI Improvements and Fixes (#3470) 2025-09-16 09:25:21 +02:00
Vadim Iskuchekov
9623e87511 feat: Simple periodic traffic reset (for Inbounds) – daily | weekly | monthly (#3407)
* Add periodic traffic reset feature model and ui with localization support

* Remove periodic traffic reset fields from client

* fix: add periodicTrafficReset field to inbound data structure

* feat: implement periodic traffic reset job and integrate with cron scheduler

* feat: enhance periodic traffic reset functionality with scheduling and inbound filtering

* refactor: rename periodicTrafficReset to trafficReset and add lastTrafficResetTime field

* feat: add periodic client traffic reset job and schedule tasks

* Update web/job/periodic_traffic_reset_job.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update web/job/periodic_client_traffic_reset_job.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update web/service/inbound.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor: rename periodicTrafficReset to trafficReset and add lastTrafficResetTime

* feat: add last traffic reset time display and update logic in inbound service

* fix: correct log message for completed periodic traffic reset

* refactor: update traffic reset fields in Inbound model and remove unused client traffic reset job

* refactor: remove unused traffic reset logic and clean up client model fields

* cleanup comments

* fix
2025-09-16 09:24:32 +02:00
Alireza Ahmadi
bc0518391e sub template enhancements 2025-09-14 23:08:09 +02:00
mhsanaei
5408a2f82c v2.8.0 2025-09-14 22:09:36 +02:00
mhsanaei
c8d71ea748 minify css 2025-09-14 20:05:15 +02:00
mhsanaei
46de886b53 windows: error filter 2025-09-14 20:04:12 +02:00
Sanaei
6d41320ed7 Merge pull request #3466 from MHSanaei/Subscription
Subscription,tgbot,rule
2025-09-14 20:03:32 +02:00
mhsanaei
bf9d2e6aeb rule: Vless Route 2025-09-14 19:53:05 +02:00
mhsanaei
ed96fa090b tgbot: subscription,qrcode, link 2025-09-14 19:51:57 +02:00
Alireza Ahmadi
3ac1d7f546 enhancements 2025-09-14 19:44:26 +02:00
mhsanaei
10025ffa66 Subscription 2025-09-14 18:56:31 +02:00
mhsanaei
5ee62b25ca clean html files
move styles to css
2025-09-12 18:46:20 +02:00
mhsanaei
311d11a3c1 cookie: MaxAge
and minor changes
2025-09-12 13:04:36 +02:00
Copilot
40b6d7707a Fix critical bugs in ObjectUtil.equals() and filterInbounds() functions (#3451)
* Initial plan

* Fix ObjectUtil.equals asymmetric comparison and filterInbounds null pointer bugs

Co-authored-by: MHSanaei <33454419+MHSanaei@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MHSanaei <33454419+MHSanaei@users.noreply.github.com>
2025-09-11 11:48:30 +02:00
mhsanaei
cbf316db31 Update check_client_ip_job.go 2025-09-11 11:10:17 +02:00
Ivan Korney
33a36ada4b fix: ru top level domain regexp option (#3450) 2025-09-11 07:06:51 +02:00
mhsanaei
82ddd10627 Fixed: update Xray Core on Windows 2025-09-10 21:12:37 +02:00
mhsanaei
2401c99817 rules: source to sourceIP 2025-09-10 18:30:40 +02:00
mhsanaei
2f36a4047c API: delClientByEmail 2025-09-10 16:36:12 +02:00
mhsanaei
dc3b0d218a Xray Core v25.9.11 2025-09-10 14:39:07 +02:00
mhsanaei
610d29765a outbound: mixed to socks 2025-09-10 12:19:09 +02:00
mhsanaei
b1ea8005e4 v2.7.0 2025-09-10 08:43:46 +02:00
mhsanaei
3f0bfa2472 Remove the buggy version of Xray core 2025-09-10 08:43:10 +02:00
mhsanaei
1e2ff650ad Xray Core v25.9.10 + GO v1.25.1 2025-09-10 08:40:08 +02:00
Sanaei
c2d6dd923f windows workflow (#3439) 2025-09-09 18:41:44 +02:00
mhsanaei
723ec25fb2 renamed dest to target 2025-09-09 14:35:21 +02:00
mhsanaei
7dc52e9a53 dokodemo-door, socks renamed to mixed, tunnel 2025-09-09 13:57:40 +02:00
Sanaei
fe9f0d1d0e api (#3434) 2025-09-09 02:32:05 +02:00
mhsanaei
18d74d54ca outbound: ECH Config List 2025-09-08 21:25:30 +02:00
mhsanaei
c7ba6ae909 add clear button 2025-09-08 21:17:48 +02:00
mhsanaei
3edf79e589 actions/setup-go@v6 2025-09-08 14:33:04 +02:00
mhsanaei
5420e643cf minor change 2025-09-08 14:32:49 +02:00
mhsanaei
9fcd0387ca Update release.yml 2025-09-08 01:12:27 +02:00
mhsanaei
7b039d219e v2.6.8 2025-09-08 00:29:30 +02:00
mhsanaei
dbec28b915 remove unsupported cipher method 2025-09-07 22:55:37 +02:00
mhsanaei
e5126806d7 xray core v25.9.5 2025-09-07 22:45:20 +02:00
Sanaei
b008ff4ad2 Vlessenc (#3426)
* mlkem768

* VlessEnc
2025-09-07 22:35:38 +02:00
Danil S.
da6b89fdcd chore: login improvements (#3408)
- added client-side form validation
- now with slow internet otp input does not appear later than all input
2025-09-04 12:11:52 +02:00
MHSanaei
d7882c25d1 removed domainMatcher 2025-09-04 12:07:39 +02:00
Ali Golzar
ed2a0a0bcf fix: prevent client updated_at from resetting when parent inbound is updated 2025-09-02 13:30:41 +03:30
Ali Golzar
4a0914cb1e feat: add "Last Online" column to client list and modal (Closes #3402) (#3405)
* feat: persist client last online and expose API

* feat(ui): show client last online in table and info modal

* i18n: add “Last Online” across locales

* chore: format timestamps as HH:mm:ss
2025-08-31 18:33:50 +02:00
Darkcyankitty
664269d513 x-ui.service hardneing (#3397) 2025-08-31 15:26:46 +02:00
Ali Golzar
d0796b26c9 fix(ui): hide Created/Updated columns and fix issues in small displays (#3400)
- Hide the “Created” and “Updated” columns in the clients
- Ensures the “All-time Traffic” column no longer overlaps with adjacent columns.
- Improves layout readability and prevents UI cluttering after the v2.6.7 update.

Closes #3399
2025-08-30 23:01:57 +02:00
mhsanaei
2750f46c01 v2.6.7 2025-08-30 16:05:33 +02:00
mhsanaei
023eb513e4 Xray Core v25.8.29 2025-08-30 10:03:32 +02:00
mhsanaei
0c7b59ed47 removed: Allocate 2025-08-28 10:15:04 +02:00
Ali Golzar
3087c1b123 Add all-time traffic for inbounds and clients (#3387)
* feat(db): add allTime field to Inbound and ClientTraffic models

* feat(inbound): increment all_time for inbounds and clients on traffic updates

calculate correct all_time traffic on migrate command

* feat(ui): show all-time traffic column for inbounds and its clients

* i18n: add pages.inbounds.allTimeTraffic label across locales

* Add All Time Traffic Usage in inbounds page top banner
2025-08-28 01:10:50 +02:00
Ali Golzar
2198397197 Created / Updated fields for clients (#3384)
* feat(backend): add created_at/updated_at to clients and maintain on create/update
backfill existing clients and set updated_at on mutations

* feat(frontend): carry created_at/updated_at in client models and round-trip via JSON

* feat(frontend): display Created and Updated columns in client table with proper date formatting

* i18n: add pages.inbounds.createdAt/updatedAt across all locales

* Update inbound.go

Remove duplicate code
2025-08-27 19:30:49 +02:00
Igor Finagin
d10c312e62 AutoFill OTP (#3381)
https://developer.apple.com/documentation/security/enabling-password-autofill-on-an-html-input-element
2025-08-25 13:42:02 +02:00
mhsanaei
24a3411465 more list for public IP address 2025-08-21 14:24:25 +02:00
Alireza Ahmand
2198e7a28f feat: Add remaining time to tgbot #3355 (#3360) 2025-08-17 13:43:25 +02:00
mhsanaei
6b23b416a7 minor changes 2025-08-17 13:37:49 +02:00
mhsanaei
16f53ce4c2 go v1.25 2025-08-17 12:27:21 +02:00
mhsanaei
27445b30e9 DNS outbound: Set "reject" as the default value for nonIPQuery 2025-08-17 12:22:33 +02:00
mhsanaei
3d0212c21d fix: fail2ban on Debian 12 #1701 2025-08-15 13:34:02 +02:00
mhsanaei
978755960f actions/checkout from 4 to 5 2025-08-14 18:41:53 +02:00
mhsanaei
9b51e9a5c5 Freedom: Add maxSplit fragment option; Add applyTo noises option 2025-08-14 18:38:56 +02:00
fgsfds
6879a8fbcb Moved DB to same app folder on Windows (#3340)
* moved db to user folder on windows

* moved db to local appdata

* made getDBFolderPath func private

* added getWindowsDbPath() func

* fix

---------

Co-authored-by: mhsanaei <ho3ein.sanaei@gmail.com>
2025-08-13 23:19:59 +02:00
mhsanaei
7258841491 Update FUNDING.yml 2025-08-12 13:00:16 +02:00
mhsanaei
23dd80fbb0 remove unnecessary ant files 2025-08-12 12:57:02 +02:00
mhsanaei
6556884c7f remove unnecessary vue files 2025-08-12 12:56:49 +02:00
Alireza Ahmadi
d5c532c64f fix saving sockopt 2025-08-09 16:07:33 +02:00
mhsanaei
ad5f774a1e Axios v1.11.0 2025-08-09 13:46:28 +02:00
g0l4
aa285914fa chore: update polygon token name (#3338) 2025-08-09 08:18:56 +02:00
mhsanaei
4d02756e1e v2.6.6 2025-08-08 20:52:04 +02:00
Alireza Ahmadi
825d93d95f upgrade telego (#3334) 2025-08-08 20:41:06 +02:00
mhsanaei
5ea6386815 better musl libc usage
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-08-08 19:55:24 +02:00
mhsanaei
d064e85ecd update dependencies 2025-08-08 18:56:47 +02:00
mhsanaei
9fc03bd10a remove ocspStapling 2025-08-08 18:55:52 +02:00
fgsfds
ae08a29cde fix: Xray restarting after being manually stopped (#2896) (#3329) 2025-08-07 23:35:11 +05:00
mhsanaei
4f25eb230e musl: new version 2025-08-06 23:35:20 +02:00
somebodywashere
ce72d53d1a fix: inbounds slow loading (#3228) (#3322)
especially encountered on big amount of clients
2025-08-06 15:42:32 +02:00
fgsfds
5e641ff9e8 Added Update all geofiles button (#3318)
* added Update all geofiles button

* localized update all string
2025-08-06 11:20:07 +02:00
Sanaei
58898e5758 Merge pull request #3317 from MHSanaei/dekodemo_sockopt
add sockopt to dockodemo
2025-08-05 16:51:26 +02:00
Alireza Ahmadi
569550d5f6 add sockopt to dockodemo 2025-08-05 14:02:23 +02:00
fgsfds
419ea63dd0 Added filters to the xray logs viewer (#3314)
* added filters to xray logs viewer

* better freedom/blackhole tags handling

* better freedom/blackhole tags handling 2

* fix comments

* fix comments 2
2025-08-05 12:10:14 +02:00
mhsanaei
6a17285935 remove: glibc check
now you can install on all OS like ubuntu 20 or 18
2025-08-04 19:16:56 +02:00
mhsanaei
4b03e9d919 v2.6.5 2025-08-04 19:12:37 +02:00
mhsanaei
7e9c3bdbaf fix: sub enable warning 2025-08-04 19:09:01 +02:00
fgsfds
957f3dbb54 Added xray access log viewer (#3309)
* added xray access log viewer

* made modal window width adaptive

* hide logs button if xray logs are disabled
2025-08-04 18:47:48 +02:00
mhsanaei
05e60af283 fix: IPLimitlog display 2025-08-04 18:23:37 +02:00
mhsanaei
5e40458116 fix: simplify error handling 2025-08-04 18:01:32 +02:00
Sanaei
baf6fdd29d fix portMap json (#3312)
Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
2025-08-04 17:16:11 +02:00
Sanaei
45f78d3521 Merge pull request #3311 from MHSanaei/dokodemo_portMap
add dokodemo port mapping
2025-08-04 17:01:57 +02:00
Alireza Ahmadi
01f984e054 add dokodemo port mapping 2025-08-04 16:45:09 +02:00
Sanaei
e4ba5ba53a add ech support (#3310)
Co-authored-by: Alireza Ahmadi <alireza7@gmail.com>
2025-08-04 16:27:57 +02:00
mhsanaei
6ff555c8bb runs-on: ubuntu-latest 2025-08-04 14:39:12 +02:00
elseif
3c1634ca7c use musl libc toolchains for build statically linked binaries (#3191) 2025-08-04 11:50:39 +02:00
mhsanaei
561c4810be default Max Age to 360min 2025-08-04 11:38:23 +02:00
mhsanaei
eb1b96643d update languages 2025-08-04 11:22:09 +02:00
mhsanaei
de5314c01f fix: pqv for sub #3306 2025-08-04 10:24:21 +02:00
mhsanaei
1088d1faf3 minor changes 2025-08-04 01:30:01 +02:00
mhsanaei
267024c43f xray core v25.8.3 2025-08-04 01:28:14 +02:00
mhsanaei
0d595f56e4 change a-input to a-textarea 2025-08-04 00:57:06 +02:00
fgsfds
a4c4f9efb3 kill process instead of sending SIGTERM on Windows (#3304) 2025-08-04 00:45:50 +02:00
mhsanaei
73a5722cca v2.6.3 2025-08-03 12:22:28 +02:00
mhsanaei
30264043f8 Xray core: old version removed 2025-08-03 12:22:06 +02:00
mhsanaei
c6062eb15c outbound: mldsa65Verify 2025-08-03 12:09:37 +02:00
mhsanaei
f1b7944828 pqv: mldsa65Verify 2025-08-03 12:01:49 +02:00
mhsanaei
7a57b31ff3 remove password type 2025-07-28 14:44:00 +02:00
mhsanaei
6e1b949081 Reality: min & max client ver 2025-07-28 13:45:47 +02:00
Mikhail Grigorev
0ad708b1b6 Added list of services for get public IP address (IP v4 and v6) (#3216)
* Fixed get public IP address

* Remove https://ifconfig.io/ip and https://ipinfo.tw/ip

---------

Co-authored-by: Mikhail Grigorev <grigorev_mm@magnit.ru>
2025-07-27 17:24:11 +02:00
X-Oracle
71f13ebcbd small improvement (#3277) 2025-07-27 17:22:59 +02:00
mhsanaei
f5f4a530cc xray core v25.7.26 2025-07-27 16:54:35 +02:00
Sanaei
702f03e4b7 Merge pull request #3279 from MHSanaei/mldsa65
add mldsa65
2025-07-27 16:52:44 +02:00
Alireza Ahmadi
487ec74e0b add mldsa65 2025-07-25 01:22:01 +02:00
mhsanaei
b4dae36345 xray core v25.7.24 2025-07-24 15:26:55 +02:00
mhsanaei
761728255c GO v1.24.5 + update dependencies 2025-07-24 15:17:01 +02:00
Azavax
b1ab156e42 Endpoint for updating client traffic by email (#3259)
* Update api.go

* Update inbound.go

* Update inbound.go
2025-07-22 23:43:48 +02:00
xujie86
fa45bf87de Update install.sh (#3267) 2025-07-22 23:28:56 +02:00
xujie86
75416eebd7 Increase the number of characters for webBasePath (#3239) 2025-07-22 12:53:12 +02:00
mhsanaei
87042d77ba ocspStapling set to 0 2025-07-22 12:49:02 +02:00
Danilo Nascimento
011e0f309a fix writeCrashReport() typo (#3218) 2025-07-10 02:36:02 +02:00
mhsanaei
b7164805f8 v2.6.2 2025-07-06 20:58:10 +02:00
mhsanaei
bbdeb65291 new alternative to get public IP address 2025-07-06 20:45:58 +02:00
Shishkevich D.
038cf34219 chore: return automatic generation of shadowsocks keys 2025-07-06 15:20:41 +07:00
Shishkevich D.
98a1517470 fix: login title shifts the input fields
* chore: revert "fix: reduced login title font-size for mobile (#3105)"

* chore: short login title translation for russian

* chore: change login title translation for ukrainian
2025-07-06 13:22:09 +07:00
mhsanaei
ce76cedb0d fixed: mux #3185 2025-07-04 14:02:33 +02:00
mhsanaei
24a313d605 fixed: type #3186 2025-07-04 13:25:24 +02:00
mhsanaei
c81c27073c v2.6.1 2025-07-02 11:28:24 +02:00
Shishkevich D.
5d11e6e13f chore: reset two-factor authentication after changing admin credentials (#3029)
* chore: add `resetTwoFactor` argument for main.go

fixes #3025

* chore: reset two-factor authentication after changing admin credentials

* chore: reset two-factor authentication after changing admin credentials

---------

Co-authored-by: somebodywashere <68244480+somebodywashere@users.noreply.github.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2025-07-02 11:25:25 +02:00
mhsanaei
f3d0b92e4a update dependencies 2025-07-02 11:17:26 +02:00
Shishkevich D.
c8c0e77714 chore: mark 2053 port as unsecured 2025-06-25 00:38:03 +07:00
Shishkevich D.
49b8f46864 fix: selecting a supported language
english could be selected by default at first load, even if the user's language was supported by the panel
2025-06-25 00:30:08 +07:00
Shishkevich D.
cad07be847 chore: russian language improvements 2025-06-23 19:42:44 +07:00
Shishkevich D.
4b20f16024 refactor: new loading logic, icons for tabs 2025-06-22 00:27:09 +07:00
Shishkevich D.
d642774a44 refactor: use new page templates 2025-06-21 15:38:43 +07:00
Shishkevich D.
1644904755 chore: clients comment improvement
- use default ant components instead custom styles
- remove comments in inbound info modal (duplicates information)
- enhance tooltip usage
2025-06-21 15:24:52 +07:00
Vadim Iskuchekov
5c10035bd9 feat: add comments under client id (#3131) 2025-06-21 14:55:35 +07:00
Shishkevich D.
2e6faf69e6 fix: generate correct keys for shadowsocks inbounds 2025-06-20 19:30:46 +07:00
Sanaei
f88b7b07f0 Merge pull request #3125 from xujie86/patch-2
Add RHEL on x-ui.sh
2025-06-19 11:36:53 +03:30
xujie86
e5752239f4 Update x-ui.sh 2025-06-19 14:38:24 +08:00
Shishkevich D.
cb22b4ad47 chore: add new dns features from v25.6.8
* chore: add new dns params

* chore: add `DNS Presets` modal

* chore: edit file names
2025-06-18 23:24:18 +07:00
spatiumstas
6a2e0071cf fix: reduced login title font-size for mobile (#3105) 2025-06-18 22:31:10 +07:00
Shishkevich D.
f86219f4de refactor: use math.MaxUint16 when checking port 2025-06-17 22:45:03 +07:00
Shishkevich D.
e272c160b1 chore: add download config button for wireguard 2025-06-17 22:25:24 +07:00
xujie86
ba50c99c10 chore: add RHEL system to install.sh
fixed #3097
2025-06-16 18:11:23 +07:00
Shishkevich D.
00b61de646 chore: add translations for routing table 2025-06-16 12:26:49 +07:00
Shishkevich D.
dff4ad31ff chore: up minimal xray-core version to v25.6.8 2025-06-16 12:22:56 +07:00
Shishkevich D.
13baf77893 chore: build image in docker compose
this makes it possible to run development build using Docker.
2025-06-13 15:25:13 +07:00
Sanaei
4531574de3 Merge pull request #3087 from rammiah/main
feat: support metrics config
2025-06-11 17:13:46 +03:30
rammiah
d1e07954c5 feat: support metrics config 2025-06-11 18:42:41 +08:00
Jay
d9922d93af chore: update the installation command (#3069)
* install command had an extra $ at the first of the line

* install command had an extra $ at the first of the line

* install command had an extra $ at the first of the line

* install command had an extra $ at the first of the line

* install command had an extra $ at the first of the line

* install command had an extra $ at the first of the line
2025-06-10 19:17:31 +03:30
Shishkevich D.
c7d315f848 chore: clean readme, add link to wiki (#3045) 2025-06-05 18:56:33 +07:00
Shishkevich D.
1781790dce fix: don't show ip limit for some protocols (#3064)
this causes the modal window to fail because protocols like wireguard, dokodemo and so on do not have clients (like vless/vmess, shadowsocks, etc).
2025-06-05 18:12:06 +07:00
spatiumstas
29f950046a feat: add command list in telegram bot (#3027) 2025-05-28 15:26:29 +07:00
Shishkevich D.
5dae785786 chore: X_UI_ENABLE_FAIL2BAN -> XUI_ENABLE_FAIL2BAN (#3030) 2025-05-22 08:21:23 +02:00
Ali Golzar
1b1cbfff42 feat: support .env file (#3013) 2025-05-17 12:33:22 +02:00
Pk-web6936
c93467b852 Code refactoring (#3011)
* Code refactoring 

read without -r will mangle backslashes
https://github.com/koalaman/shellcheck/wiki/SC2162

* Update x-ui.sh
2025-05-16 20:23:57 +02:00
Shishkevich D.
c988d55256 fix: handle inbounds interaction errors (#3009)
eliminates messages like: “Inbound created successfully (Port 100 is already busy)”.
2025-05-16 23:56:56 +07:00
mhsanaei
ef625c75d8 v2.6.0 2025-05-16 14:26:20 +02:00
mhsanaei
182e591c48 Xray-core v25.5.16 2025-05-16 13:05:46 +02:00
Columbiysky
3666d1193f fix: Restore from .db file fails (#2988)
* fix: issue 2953. Restore from .db file fails because

* Update server.go
2025-05-14 17:35:53 +02:00
mhsanaei
7a5a833af3 update dependencies 2025-05-11 13:05:45 +02:00
Tara Rostami
58f978bb0a fix: 2fa qr-code (#2996) 2025-05-11 02:12:43 +02:00
ckun52880
6d47496069 chore: simplified chinese translate improved 2025-05-10 22:42:23 +07:00
Shishkevich D.
e5c19759db fix: remove duplicate path 2025-05-10 22:25:24 +07:00
Shishkevich D.
295a8b6e37 chore: сonfiguring paths for CI
no need for automatic build when changing files that do not affect panel operation
2025-05-10 22:24:49 +07:00
Shishkevich D.
384e23aeb2 chore: customizing triggers for builds
Now CI triggers on commit to main, or on release
2025-05-10 22:00:20 +07:00
Shishkevich D.
23293813bb chore: add translations for a-table 2025-05-10 21:47:59 +07:00
Columbiysky
c15ec5315a chore: russian translate improved (#2990) 2025-05-10 19:41:53 +07:00
Shishkevich D.
1ddfe4aba3 chore: toasts translation refactoring 2025-05-09 10:46:29 +07:00
Shishkevich D.
fe3b1c9b52 chore: implement 2fa auth (#2968)
* chore: implement 2fa auth

from #2786

* chore: format code

* chore: replace two factor token input with qr-code

* chore: requesting confirmation of setting/removing two-factor authentication

otpauth library was taken from cdnjs

* chore: revert changes in `ClipboardManager`

don't need it.

* chore: removing twoFactor prop in settings page

* chore: remove `twoFactorQr` object in `mounted` function
2025-05-08 16:20:58 +02:00
nistootsin
d39ccf4b8f Added 3 new buttons to telegram bot (#2965)
* Add a new button to but : Reset All Clients

* handel translation for `Reset All Clients` button

* refactoring

* add a new button to telegram bot >> `Sorted Traffic Usage Report`

* - refactoring

* add ip limit conifg on new client adding time
2025-05-06 18:27:17 +02:00
Shishkevich D.
1aed2d8cdc feat: implement geofiles update in panel (#2971)
solves #2672

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
2025-05-06 18:10:58 +02:00
mhsanaei
c3084aaece geosite : category-porn 2025-05-06 09:55:06 +02:00
Shishkevich D.
13cf7271d6 fix: check default credentials during a fresh installation 2025-05-06 09:05:32 +07:00
Shishkevich D.
63edc63ab0 chore: do not show the current login and password (#2969) 2025-05-03 18:00:25 +07:00
Columbiysky
85cbad3ef4 feat: hashing user passwords
solves problems #2944, #2783
2025-05-03 16:27:53 +07:00
mhsanaei
3d54e33051 v2.5.8 2025-04-30 11:24:06 +02:00
mhsanaei
01be9fec95 Xray Core v25.4.30 2025-04-30 10:56:55 +02:00
mhsanaei
0306e75c2a update dependencies 2025-04-30 10:51:49 +02:00
Павел
255ff9cc20 refactror: add check ipv6 path
fix issues #1669 #2882
2025-04-29 20:32:54 +07:00
Tara Rostami
2fbb1ca6c9 chore: minor fixes for login page 2025-04-27 11:22:43 +07:00
Shishkevich D.
3b47028060 chore: new templates for issues and pull requests (#2935)
* chore: new issue templates

* chore: fixing templates

* chore: add pull request template
also edited bug report

* chore: add checklist for question and feat request template

* chore: remove title prefix

* fix: template title

* fix: re-fixing the template title

* chore: remove checklist for pull request

* chore: remove emojies

* fix: elimination of minor defects
2025-04-23 09:04:36 +02:00
Shishkevich D.
d9ab8b4ce4 fix: qr modal header 2025-04-19 22:43:24 +07:00
Shishkevich D.
e6389f3fb3 chore: move qr params in a-popover 2025-04-19 22:36:17 +07:00
AKILA INDUNIL
96fd7d0e7c feat: add a toggle to use public IPv4 in QR/URI 2025-04-19 22:32:22 +07:00
Pk-web6936
cf02f02210 automatic Build and Release (#2919)
* Update release.yml

* Update release.yml

* Update release.yml
2025-04-18 22:32:38 +02:00
Pk-web6936
4dc8974af0 docs: Update README (#2921)
* Update README.es_ES.md

* Update README.ru_RU.md
2025-04-18 22:31:35 +02:00
006lp
b527a528ea docs: Update README.zh_CN.md (#2920) 2025-04-18 21:16:39 +02:00
Shishkevich D.
1a53af0434 chore: deleting unnecessary functions 2025-04-18 17:55:09 +07:00
nistootsin
be8d55dadb feat: add Submit As Enable in telegram bot 2025-04-16 15:16:55 +07:00
Shishkevich D.
d54e7a9b14 fix: encoding subscription title in base64 2025-04-15 19:29:54 +07:00
Shishkevich D.
45c3d730d4 fix: Error when generating shadowsocks keys in Blake3_AES_256_GCM 2025-04-15 18:33:26 +07:00
Columbiysky
aab01ff11a fix docker-compose.yml: the attribute version is obsolete (#2891) 2025-04-12 08:19:55 +02:00
mhsanaei
236dddf482 v2.5.7 2025-04-11 12:11:39 +02:00
mhsanaei
8e472838d8 update dependencies 2025-04-11 11:09:43 +02:00
Pk-web6936
b75a1ef5e1 Code refactoring (#2877)
* read without -r will mangle backslashes.

https://github.com/koalaman/shellcheck/wiki/SC2162

* read without -r will mangle backslashes.
2025-04-09 11:12:14 +02:00
kmoshax
d956f78347 feat: add Arabic language (#2880)
* translation: add Arabic support into language manager

* translation: add Arabic language support

* translation: add Arabic language support in README files
2025-04-08 22:26:05 +07:00
Shishkevich D.
8ef447a997 chore: create FileManager class for downloading files 2025-04-08 22:17:29 +07:00
Shishkevich D.
520f7a2d15 fix: current tab highlight in sidebar (#2874) 2025-04-07 08:28:02 +07:00
mhsanaei
3ded4ee658 minor changes 2025-04-07 00:45:52 +02:00
Shishkevich D.
bea19a263d Code refactoring (#2865)
* refactor: use vue inline styles in entire application

* refactor: setting row in dashboard page

* refactor: use blob for download file in text modal

* refactor: move all html templates in `web/html` folder

* refactor: `DeviceUtils` -> `MediaQueryMixin`
The transition to mixins has been made, as they can update themselves.

* chore: pretty right buttons in `outbounds` tab in xray settings

* refactor: add translations for system status

* refactor: adjust gutter spacing in setting list item

* refactor: use native `a-input-password` for password field

* chore: return old system status
with new translations

* chore: add missing translation
2025-04-06 11:40:33 +02:00
mhsanaei
878e0d02cd Xray Core v25.3.31
+update dependencies
2025-04-04 21:20:07 +02:00
Pk-web6936
b15ea1f74d chore: update Go to v1.24.2 (#2866)
* Go v1.24.2

* Update dependencies
2025-04-04 21:25:48 +07:00
Shishkevich D.
431d7350a5 chore: simplify login page (#2851)
* chore: change login page

* chore: minor improvements on login page

* chore: add login button padding

* fix: delete unnecessary attributes

* fix: Restore headline animation with DOMContentLoaded
2025-04-01 14:24:03 +02:00
Shishkevich D.
127bea7f73 fix: opening links in the sidebar 2025-03-30 22:02:05 +07:00
Pk-web6936
7c58bcbb46 Consolidate and Optimize .gitignore Files (#2838)
* Update .gitignore
2025-03-30 11:32:42 +07:00
Columbiysky
fec9b25248 locs(RU\UA): a bit better translate (#2841) 2025-03-30 11:29:35 +07:00
nistootsin
728166bd1a Add Admin-Controlled Client Management to Telegram Bot (#2788)
* Add feature to add clients to inbound:
- Implement buttons for adding new clients
- Handle client addition process (submission remains to be completed)
- Support for multiple languages

* update the go.mod

* feat: complete submission process for adding a client to inbounds

* - Add client variables: client_method, client_sh_password, client_tr_password
- Exclude specific inbound protocols (HTTP, WireGuard, Socks, DOKODEMO) from addclient inline button

* - customize the add client message and json for each protocol

* - handle password input rather than id for shadow and trojan protocols

* - remove add_client_as_enable button in bot

* restructrure the add client bot feature

* update all files in web/translation/

* Refactoring

* - add traffic button to add client bot feature

* - fix a mistake in the email prompt message

* - add expire data button to add client telegram process.

* Refactroring

* remove refresh button in add client

* - delete message after cancel

* - uptimize the process of adding client by deleting main message on
  getting text inputs.
2025-03-26 19:16:35 +01:00
Sanaei
d376ce057c Merge pull request #2823 from shishkevichd/refactor/refactor-5
Code refactoring
2025-03-26 13:04:42 +01:00
Sanaei
5e6e900e64 Merge branch 'main' into refactor/refactor-5 2025-03-26 11:29:38 +01:00
danilshishkevich
19f7938617 chore: giving keys for each a-collapse-panel 2025-03-25 15:13:17 +00:00
danilshishkevich
fe791b6e99 chore: move client table into components 2025-03-24 15:17:45 +00:00
danilshishkevich
a02bf3195d chore: improve styles
- elements with class `.collapse-title` are missing
- changed paddings in `.ant-xray-version-list-item` element
2025-03-24 12:22:12 +00:00
Shishkevich D.
3ea05d30c1 chore: transforming a common sidebar into a separate component
- also added saving collapsed state
2025-03-24 11:19:27 +00:00
Shishkevich D.
40ebf2902e fix: dashboard won't load 2025-03-24 10:02:01 +00:00
Shishkevich D.
14253c3586 chore: moving the modals to a separate directory 2025-03-24 09:57:37 +00:00
Shishkevich D.
7b15274c84 chore: moving the settings tabs to a separate directory 2025-03-24 09:45:15 +00:00
mhsanaei
6545d8b61d glibc version
replace with OS check
2025-03-22 07:48:50 +01:00
Shishkevich D.
6f4eefe601 chore: make class to get the device form factor 2025-03-21 15:09:05 +00:00
Shishkevich D.
510c35f450 chore: meta tag change
- `X-UA-Compatible` is only used by Internet Explorer, so it removed
- `robots` allows the panel not to be indexed by a search engine
2025-03-21 14:49:49 +00:00
Shishkevich D.
00addb0dd9 chore: delete display.css
these styles are not used anywhere
2025-03-21 14:38:41 +00:00
Shishkevich D.
db140a1e9b chore: improve russian translation (#2802)
* chore: improve russian translation

* chore: corrections in translation
2025-03-21 18:54:02 +07:00
mhsanaei
db945e2fbd OS: Alma Linux 9.5+ 2025-03-21 12:49:23 +01:00
mhsanaei
667fac15f4 OS: Rocky Linux 9.5+ 2025-03-21 12:40:13 +01:00
mhsanaei
29033a7828 OS: Debian 12+ 2025-03-20 19:45:29 +01:00
mhsanaei
2ffde55f8f v2.5.6 - Happy Nowruz 2025-03-20 10:54:54 +01:00
Shishkevich D.
6e5ed881f2 chore: pretty Inbounds page (#2791)
* chore: pretty 'Inbounds' page

* chore: return styles for aCustomStatistic

styles was intended to properly display a-statistic in the app, but for some unknown reason it was removed

* fix: switch style in dark mode

---------
2025-03-18 22:13:01 +01:00
Kirill Dunaev
d52c50fd9e Russian translation fixes (#2792) 2025-03-18 20:56:58 +07:00
somebodywashere
fa5fb927c1 Update to regular cert issue (#2790) 2025-03-18 13:47:58 +01:00
mhsanaei
f7198c4c2f Update index.html 2025-03-18 13:39:07 +01:00
Tara Rostami
21e7d45b54 Fixes and improvements (#2789)
* Fixes and improvements

* Update translate.en_US.toml
2025-03-18 09:51:05 +01:00
Shishkevich D.
db62a07fb8 Code refactoring (#2785)
* chore: pretty theme menu in sidebar

* refactor: renaming component templates

* refactor: create custom `a-statistic` component

* fix: display button text only on large screens

* chore: remove loading background in overview page

* fix: show `Version` text when xray version is unknown
2025-03-17 18:26:07 +07:00
somebodywashere
e3120c4028 Updates to CF cert issue (#2780) 2025-03-17 09:12:52 +01:00
Shishkevich D.
7ae855e7c9 chore: some improvements (#2782)
* chore: improve outbound link input

* chore: ui improvement
2025-03-17 08:26:59 +07:00
Shishkevich D.
b9307c6c9c chore: pretty 'Overview' page (#2772)
* chore: pretty 'Overview' page

* chore: some improvements in 'overview page'
- reduced font size
- added caption to buttons
- fixed display of xray state
- xray version display returned
2025-03-15 12:15:46 +01:00
Ilya Afanasov
d30cdbf49a feat: custom subscription title in panel (#2773)
* feat: custom subscription title in panel

* feat: added translations
2025-03-15 08:16:59 +01:00
Sanaei
cac00224db runs-on: ubuntu-22.04 (#2767)
https://github.com/actions/runner-images/issues/11101
2025-03-13 16:06:08 +01:00
mhsanaei
b68f0a206c xray log - minor change 2025-03-13 11:48:00 +01:00
mhsanaei
0bde51b91e Refactor: Use any instead of interface{} 2025-03-12 20:43:43 +01:00
mhsanaei
280a22b57d warp - optimize utility code 2025-03-12 19:27:19 +01:00
mhsanaei
315d852087 fix - public IP #2763 2025-03-12 19:20:13 +01:00
mhsanaei
6a0d2e0a29 Axios v1.8.2 2025-03-11 13:18:52 +01:00
Shishkevich D.
a811225610 fix: protocol checking during random uuidv4 generation
fixes the https://github.com/MHSanaei/3x-ui/issues/2750 issue
2025-03-10 22:09:51 +07:00
mhsanaei
1893c3814d v2.5.5 2025-03-10 14:33:58 +01:00
mhsanaei
422c391f96 Xray log: show failed on error log level 2025-03-10 14:31:06 +01:00
mhsanaei
f7f95ffbae bug fix - xray log 2025-03-10 13:46:46 +01:00
mhsanaei
f408bd7c77 Xray core v2.3.6 + update dependencies 2025-03-10 09:51:31 +01:00
Shishkevich D.
ad13ce6cde fix: generating shortIds for vless reality (#2745) 2025-03-09 19:37:53 +07:00
Shishkevich D.
c35179d924 chore: remove unused variable 2025-03-09 06:38:45 +00:00
Shishkevich D.
cedc7f0fb8 chore: refactoring RandomUtil class
now we use window.crypto.getRandomValues to generate random values.
2025-03-09 06:37:05 +00:00
Shishkevich D.
64fa0e97a3 chore: use crypto.randomUUID() for generating UUIDv4 2025-03-09 06:09:42 +00:00
Shishkevich D.
a45e9de472 chore: use Base64 library for generating SS password 2025-03-09 06:06:27 +00:00
Shishkevich D.
101e9ebf35 fix: modals style 2025-03-09 06:01:27 +00:00
Shishkevich D.
17a76d2843 Revert "chore: add missing params for grpc stream settings (outbound)"
This reverts commit 1c59afe031.
2025-03-09 05:38:34 +00:00
Shishkevich D.
a23a5de540 Revert "chore: add new grpc params for outbound (#2744)"
This reverts commit c49ec9a74c.
2025-03-09 05:37:50 +00:00
Shishkevich D.
a16e83468b chore: add dns type for kcp protocol
see https://xtls.github.io/config/transports/mkcp.html#headerobject
2025-03-09 12:23:35 +07:00
Shishkevich D.
1c59afe031 chore: add missing params for grpc stream settings (outbound) 2025-03-09 04:49:17 +00:00
Shishkevich D.
c49ec9a74c chore: add new grpc params for outbound (#2744) 2025-03-09 11:28:12 +07:00
mhsanaei
a0dd101d97 tgbot - restart
change restart force to restart
2025-03-08 23:08:04 +01:00
mhsanaei
700cf9c10b minor changes 2025-03-08 18:14:48 +01:00
Shishkevich D.
697cd5e6d9 Code refactoring (#2739)
* refactor: switching to the use of typed props

* refactor: `password-input` -> `a-password-input`

* fix: qr modal copy error
2025-03-08 22:41:27 +07:00
Shishkevich D.
c6d27a4463 chore: pretty backup and xray version modal (#2737)
* chore: pretty `backup & restore` modal

* chore: pretty `xray version` modal

* fix: new `xray version` modal style
2025-03-08 20:41:23 +07:00
Shishkevich D.
751f564c4a fix: base64 encoding on vmess/shadowsocks inbounds (#2736) 2025-03-08 12:33:34 +07:00
Shishkevich D.
6658f648e6 refactor: move language manager to utils (#2735) 2025-03-08 09:54:41 +07:00
Shishkevich D.
d6f9f3f6d3 chore: add empty screens for empty data (balancers, reverses, dns) 2025-03-07 22:17:14 +07:00
Shishkevich D.
fad6c497eb chore: add empty screens for empty data (balancers, reverses, dns)
cleaned up some of the margins & paddings
2025-03-07 13:56:03 +00:00
Sanaei
42fa64770b Merge pull request #2732 from shishkevichd/refactor/refactor-1
Code refactoring
2025-03-07 12:40:35 +01:00
Shishkevich D.
0a207b8a2c refactor: merging all util functions into classes 2025-03-07 09:07:23 +00:00
Shishkevich D.
26bf693dbd refactor: move copy function to utils.js 2025-03-07 07:27:33 +00:00
Shishkevich D.
7483fb2ec5 refactor: delete base64js
instead of base64 library you can use built-in JS functions `btoa()` and `atob()`
2025-03-07 07:11:03 +00:00
Shishkevich D.
2d8cca3a2e refactor: delete clipboardjs (#2727)
text copying can be done without using additional libraries
2025-03-06 20:43:46 +01:00
UltraMegaPotato
cf7fec1351 Change the cpu usage interval from second to minute (#2729)
feature(check_cpu_usage): Change the usage interval from second to minute
2025-03-06 20:38:58 +01:00
mhsanaei
361849b9db Balancer fallbackTag #2724 2025-03-06 20:35:17 +01:00
Shishkevich D.
c13db7922e Pretty Panel and Xray settings (#2726)
* chore: refactor `setting-list-item` component

* chore: remove padding

* chore: replace settings list with settings collapse panels

* chore: add missing translations

* chore: fix translation
2025-03-06 11:17:25 +01:00
mhsanaei
d6d05a9b4d v2.5.4 2025-03-05 15:42:51 +01:00
mhsanaei
3caace2cb6 update dependencies 2025-03-05 15:40:27 +01:00
Shishkevich D.
99f26be30d feat: add statistics section (#2718)
the "Outbounds Traffic" parameter, which was misleading, was also renamed and moved
2025-03-05 13:27:25 +01:00
mhsanaei
b0edd24c52 Noise: Add hex 2025-03-05 13:22:07 +01:00
mhsanaei
f0cfd48f66 Sockopt: Add addressPortStrategy 2025-03-05 13:14:17 +01:00
Shishkevich D.
f5aea03765 chore: add global params for DNS (#2713)
parameter list: `disableCache`, `disableFallback`, `disableFallbackIfMatch`, `clientIp`
2025-03-04 14:18:51 +01:00
mhsanaei
14cdde371f better view for uTLS 2025-03-04 12:50:49 +01:00
mhsanaei
99a23f25d5 TLS-REALITY : hide value of private key 2025-03-04 10:36:02 +01:00
Shishkevich D.
653ec90451 chore: pretty dns section in xray settings (#2700) 2025-03-04 09:55:13 +01:00
Shishkevich D.
91a84db479 chore: pretty auth tab in panel settings (#2701) 2025-03-04 09:54:59 +01:00
mhsanaei
0f97eca314 Xray core v25.3.3 2025-03-04 09:54:10 +01:00
mhsanaei
fb79081aa1 TLS fingerprints: randomizednoalpn 2025-03-03 10:20:52 +01:00
mhsanaei
ea19fb8ff6 fix mistake 2025-03-01 10:19:52 +01:00
atarwn
f4cfe9eb63 Improved ru_RU translation (#2677)
* Improved ru_RU translation

Some words have been changed to sound nicer. I made some words less complicated to understand. I also fixed some warnings and messages so they are easier to read.

* Apply suggestions from code review
2025-02-28 10:42:27 +01:00
mhsanaei
4def70a006 Xray core buggy version removed 2025-02-27 13:40:56 +01:00
mhsanaei
e0e9e2681a docker: go 1.24 2025-02-25 18:56:35 +01:00
mhsanaei
31e1581d6b v2.5.3 2025-02-25 18:43:29 +01:00
mhsanaei
018e98a510 Go v1.24.0 2025-02-25 18:43:15 +01:00
mhsanaei
21ea673c30 Make wget verify certificates part2 #2661
Co-Authored-By: İrem Kuyucu <siren@kernal.eu>
2025-02-24 13:15:18 +01:00
Shishkevich D.
08b55da408 feat: add quic protocol in xray rule modal (#2666) 2025-02-24 09:23:59 +01:00
atarwn
7a3ee69a7f Virtuozzo linux support (#2668) 2025-02-24 09:22:34 +01:00
mhsanaei
664bd9b596 bug fix #2660 2025-02-22 14:31:08 +01:00
mhsanaei
ceb1217121 serverNameToVerify to verifyPeerCertInNames #2662 2025-02-22 14:09:52 +01:00
mhsanaei
e754523689 Xray core v25.2.21 2025-02-22 13:46:15 +01:00
İrem Kuyucu
e84503feec Make wget verify certificates (#2661) 2025-02-22 11:53:36 +01:00
AAA
1bbf31df9f feat(externalTrafficJob): External Traffic Inform (#2660)
* Add Setting entity + GUI field in panel settings

* Add a missing 'Traffic' in InformEnabale field

* Add ExternalTrafficURL Post request call

* Add translation + cleanup

* Move options to General tab

---------

Co-authored-by: root <root@vm3562019.stark-industries.solutions>
Co-authored-by: root <root@vm3688062.stark-industries.solutions>
2025-02-22 10:45:14 +01:00
mhsanaei
49bfff9fa5 v2.5.2 2025-02-04 11:38:57 +01:00
Sanaei
d18a1a37ce revert group management (#2656)
* Revert "json post base path bug fixed (#2647)"

This reverts commit 04cf250a54.

* Revert "Group Management of Subscription Clients"

* Revert "fix getSubGroupClients for enable/disable and edit clients."

* Revert "Enhance database initialization in db.go (#2645)"

This reverts commit 66fe84181b.

* Revert "Add checkpoint handling in CloseDB function (#2646)"

This reverts commit 4dd40f6f19.

* Revert "Improved database model migration and added indexing (#2655)"

This reverts commit b922d986d6.
2025-02-04 11:27:58 +01:00
mhsanaei
04c6b2722b README: Persian 2025-02-03 20:30:42 +01:00
mhsanaei
94d651fc93 v2.5.1 2025-02-03 17:40:33 +01:00
mhsanaei
aae0cb37b7 Xray Core v25.1.30 2025-02-03 17:37:01 +01:00
Zahar Izmailov
b922d986d6 Improved database model migration and added indexing (#2655) 2025-02-03 13:36:03 +01:00
Abolfazl Fazilat
8a7cffd63f Completed translation for missing text (#2653) 2025-01-31 18:54:52 +01:00
Sanaei
c8e8c97afc Merge pull request #2652 from Incognito-Coder/main
Some Improvement
2025-01-31 16:01:14 +01:00
AghayeCoder
46ba4c4518 fix getSubGroupClients for enable/disable and edit clients. 2025-01-31 17:27:09 +03:30
AghayeCoder
a787ab497c switcher for outbound traffic 2025-01-31 17:24:03 +03:30
mhsanaei
3be204f272 v2.5.0 2025-01-28 00:09:20 +01:00
mhsanaei
de13729a97 update dependencies 2025-01-28 00:09:12 +01:00
mhsanaei
468eb8b908 Update release.yml
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-01-27 01:13:28 +01:00
mhsanaei
e95a748e77 docker
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-01-27 01:13:12 +01:00
mhsanaei
34e2d961f5 quiet build
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-01-27 01:09:39 +01:00
mhsanaei
b4a1d81444 [subJson] better direct options
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2025-01-26 19:43:02 +01:00
mhsanaei
46ef506aa6 TLS - serverNameToVerify 2025-01-26 19:33:50 +01:00
mhsanaei
51220917c4 outbound Traffic - default false
if you need it you need to change it manually to true
2025-01-26 11:22:42 +01:00
mhsanaei
b34956647b minor change 2025-01-26 11:07:45 +01:00
mhsanaei
5c4e2dfd39 default setting - scStreamUpServerSecs 2025-01-26 11:07:35 +01:00
Ivan Zorin
dd4c2adb37 Fix non-MultiUser dbInbounds (#2649) 2025-01-26 11:03:06 +01:00
mhsanaei
2dec7f48f5 bug fix - get client ips 2025-01-24 17:16:16 +01:00
Ali Rahimi
04cf250a54 json post base path bug fixed (#2647)
* json post base path bug fixed

* added comment field to group client management
2025-01-23 21:33:47 +01:00
mhsanaei
ac9ab828b5 GO v1.23.5 + update dependencies 2025-01-21 23:04:03 +01:00
Zahar Izmailov
4dd40f6f19 Add checkpoint handling in CloseDB function (#2646)
* Add checkpoint handling in CloseDB function

---------

Co-authored-by: Zakhar Izmaylov <ptdev@kedruss.ru>
2025-01-21 22:55:21 +01:00
mhsanaei
7911eeb69f XHTTP - scStreamUpServerSecs 2025-01-21 22:43:13 +01:00
Ali Rahimi
6e9180a665 Group Management of Subscription Clients (#2644)
* add group user with the same subscription id to all inbounds

* code format compare

* add await for reset client traffic

* en language changed

* added client traffic syncer job

* handle exist email duplicate in sub group

* multi reset and delete request for clients group

* add client traffic syncer setting option

* vi translate file updated

* auto open qr-modal bug fixed
2025-01-21 03:01:54 +01:00
Zahar Izmailov
66fe84181b Enhance database initialization in db.go (#2645)
- Updated GORM configuration to skip default transactions and prepare statements.
- Modified the database connection string to include caching and journal mode settings.
- Executed several PRAGMA statements to optimize SQLite performance and enable foreign key support.

These changes improve database handling and performance in the application.

Co-authored-by: Zakhar Izmaylov <ptdev@kedruss.ru>
2025-01-21 02:59:30 +01:00
Tara Rostami
7b7eb98acb Minor Fixes (CSS) (#2641) 2025-01-15 12:50:50 +01:00
Alex Churin
7eb5afdd8d wireguard modal fix (#2640) 2025-01-14 16:08:19 +01:00
xtclovver
522ccda71c Update translate.ru_RU.toml (#2637)
Fixed ru translation
2025-01-13 18:23:19 +01:00
mortefy
f780efb430 Fixed ru translation (#2638) 2025-01-13 18:22:35 +01:00
mhsanaei
783f1a073e time Location - local 2025-01-11 15:13:49 +01:00
Dmitiry Vinogradov
a4c38ec8ae fail2ban service in docker container (#2632)
docker container

Co-authored-by: Dmitrij Vinogradov <dmitrij.vinogradov@gmail.com>
2025-01-11 13:41:48 +01:00
Igor Semenov
0c47771671 Add Russian domains (#2635)
Add Russian domains
2025-01-11 13:39:32 +01:00
Tara Rostami
67920a1962 Minor Fixes (UI) (#2636)
* Minor Fixes (UI)

* Update custom.min.css
2025-01-11 13:38:26 +01:00
mhsanaei
49d3957c07 bug fix 2025-01-05 21:04:18 +01:00
mhsanaei
ee946ceab2 iplimit: ipRegex improved
When the client has MUX enabled, a TCP or UDP prefix appears before the IP address. We initially weren’t aware of this behavior, but we have now resolved the issue.
2025-01-05 18:58:51 +01:00
LoST
b650064177 Firewall improvements (#2630)
* The menu has been completed

*Added firewall shutdown

*Improved port removal process (optional)
2025-01-05 16:01:56 +01:00
mhsanaei
9fb9d7201e Add custom v2ray rules for Russia
runetfreedom/russia-v2ray-rules-dat
2025-01-05 15:39:40 +01:00
mhsanaei
4a3b9b913d bug fix - reality settings 2025-01-01 22:49:29 +01:00
mhsanaei
da674d44cf httpupgrade: remove host from header 2025-01-01 20:18:28 +01:00
LoST
cc3252531b Added "comment" in all languages (#2631)
- Added a "comment" in all languages

---------

Co-authored-by: mhsanaei <ho3ein.sanaei@gmail.com>
2025-01-01 19:13:55 +01:00
mhsanaei
284731deeb xmux - hMaxReusableSecs 2025-01-01 18:48:47 +01:00
mhsanaei
9bc5c1d070 tcpNoDelay to penetrate 2025-01-01 18:42:50 +01:00
mhsanaei
26a7700557 Xray core buggy version removed
v1.8.24 and
major >=25.1.1
2025-01-01 18:36:46 +01:00
mhsanaei
b6a919218a Xray Core v25.1.1 2025-01-01 18:30:36 +01:00
mhsanaei
6cc07254e0 Fallback - Reality
this is not my problem they don't mention it on their document
2024-12-30 00:38:49 +01:00
mhsanaei
4ad5a5aba4 update dependencies 2024-12-28 15:15:01 +01:00
mhsanaei
7ab8164de4 Xray Core v24.12.28 2024-12-28 15:14:39 +01:00
MHSanaei
195effd177 v2.4.11 2024-12-27 21:49:45 +01:00
MHSanaei
747ad3b9c8 bug fix - outbound xhttp link 2024-12-27 21:30:51 +01:00
mhsanaei
cf879f9527 bug fix - vmess tls 2024-12-27 17:59:59 +01:00
mhsanaei
04c658f1a0 Client: Comment
now you can add Comment and write anything you want to remember
2024-12-27 13:48:07 +01:00
mhsanaei
2ab1a174db moment v2.30.1 2024-12-25 20:27:48 +01:00
mhsanaei
dfca2af997 axios v1.7.9 2024-12-25 19:09:54 +01:00
Baton34
0a7d15b48c RU: translation fix (#2620) 2024-12-22 14:10:06 +01:00
mhsanaei
518bc72f90 media - telebot 2024-12-21 14:00:21 +01:00
mhsanaei
3c65209ce9 Stargazers over Time - variant=adaptive 2024-12-21 13:52:05 +01:00
LoST
174535b05d unnecessary sudo removed (#2619)
All the commands do not work exactly, but since we already work in the beginning on behalf of root, then sudo is not necessary at the beginning. Is that right? 

P.S. when selected in the menu, it says that "command not found".
2024-12-20 23:48:10 +01:00
Tara Rostami
fff54fe7f3 fix bug (#2618) 2024-12-20 19:06:19 +01:00
LoST
0859d230b0 Firewall management: improved (#2614)
* fix permissions

* Update install func + add/edit func open/close ports + status firewall

* hotfix

* subport
2024-12-20 18:43:47 +01:00
KiselevAlexander
02998c5467 Added ip limit data and controls to client info modal (#2617) 2024-12-20 18:34:30 +01:00
Tara Rostami
3f38c42852 fail2ban: better ipv6 validation (#2615) 2024-12-20 18:33:27 +01:00
mhsanaei
49295661fd fail2ban - unban fix
sorry for my mistake
2024-12-19 23:24:10 +01:00
mhsanaei
e4301f8d93 fail2ban - allports 2024-12-19 22:49:25 +01:00
mhsanaei
c6586f8df2 fix typo 2024-12-19 14:53:09 +01:00
mhsanaei
8e81008cdc v2.4.10 2024-12-19 10:54:17 +01:00
mhsanaei
e33ad809d6 Xray Core v24.12.18 2024-12-19 10:48:43 +01:00
mhsanaei
d804043a18 fail2ban - bantime 30min 2024-12-18 13:23:32 +01:00
mhsanaei
0fb0df7056 fail2ban - ipv4 & 6 2024-12-18 12:31:05 +01:00
mhsanaei
73e90e0eaa TLS, REALITY : fingerprint set default to chrome 2024-12-17 23:25:54 +01:00
mhsanaei
44ef1ac9a6 remove insecure cipher suites list 2024-12-17 23:18:16 +01:00
mhsanaei
aeac7f2c8b UTLS: unsafe 2024-12-17 23:17:12 +01:00
mhsanaei
39ef172b87 fail2ban - ban and unban an IP Address 2024-12-17 17:59:04 +01:00
mhsanaei
0df85cc3d9 XHTTP: server & client
Remove scMinPostsIntervalMs, xmux, noGRPCHeader from the server side and add them to the client side.

Before you could have them on sub json but I decided to remove them.
2024-12-17 10:39:37 +01:00
MHSanaei
f0f4f082ae improve iplimit 2024-12-16 14:26:47 +01:00
MHSanaei
b29bd993d4 fix session
twice  set-cookie bug fixed
2024-12-16 14:24:59 +01:00
MHSanaei
127eaf69b6 fail2ban - unban all without restart 2024-12-16 13:28:53 +01:00
mhsanaei
36b0289bc6 XHTTP: Add "scMaxBufferedPosts" 2024-12-16 11:20:38 +01:00
MHSanaei
0abd0be725 v2.4.9 2024-12-16 10:13:40 +01:00
MHSanaei
918a2b1533 XHTTP: Add "hMaxRequestTimes, hKeepAlivePeriod" 2024-12-16 10:13:01 +01:00
MHSanaei
88a17cd227 Xray core v24.12.15 2024-12-16 10:00:15 +01:00
mhsanaei
b60387accb XHTTP: Add "keepAlivePeriod" 2024-12-11 17:05:47 +01:00
mhsanaei
049177024b Xray core buggy version removed
accept >= v24.11.30 or v1.8.24
2024-12-05 20:17:47 +01:00
mhsanaei
9c63638af1 xhttp - default settings 2024-12-05 20:15:19 +01:00
mhsanaei
67dfe664a6 GO v1.23.4 + update dependencies 2024-12-05 20:14:56 +01:00
mhsanaei
4efcdb3e01 Transport: Remove HTTP
Migrated to XHTTP "stream-one" mode.
2024-12-04 13:49:43 +01:00
Sanaei
ddc2cfacb9 Delete .github/dependabot.yml 2024-12-03 23:15:02 +01:00
mhsanaei
337729529a Xray Core v24.11.21 2024-12-03 23:09:38 +01:00
mhsanaei
4c4cc362b3 fix crash report
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-12-03 23:07:24 +01:00
mhsanaei
4e0aca16c2 [warp] report error in change license
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-12-03 23:01:32 +01:00
lexnake3
749a426a71 show IP on warning log when user pass is not correct (#2607) 2024-12-03 22:41:14 +01:00
mhsanaei
b859327b8a splithttp to xhttp 2024-12-03 22:24:34 +01:00
mhsanaei
3e8fc59213 WebSocket: Add heartbeatPeriod 2024-12-03 22:07:58 +01:00
mhsanaei
462e02140d XHTTP: Add "stream-one" 2024-12-03 21:57:44 +01:00
mhsanaei
6b41df2d89 update dependencies 2024-12-03 21:33:01 +01:00
Tara Rostami
eb5ed5c0dd readme update (#2604) 2024-11-21 20:48:30 +03:30
mhsanaei
e0bbacf013 v2.4.8 2024-11-21 18:12:19 +03:30
mhsanaei
34af7f8bfa README: Languages 2024-11-21 18:10:26 +03:30
mhsanaei
b569c21fec XHTTP: noGRPCHeader 2024-11-21 17:56:25 +03:30
mhsanaei
c4a5c059e3 Xray Core v24.11.21 2024-11-21 17:31:21 +03:30
mhsanaei
c21ed90da0 Core crash report
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-11-21 17:25:11 +03:30
mhsanaei
9b58277945 speedtest - snap install 2024-11-21 01:18:32 +03:30
mhsanaei
5a4a42aeb8 update dependencies 2024-11-21 00:37:44 +03:30
mhsanaei
9476472bf6 AsIs - freedom DS 2024-11-21 00:31:45 +03:30
Alireza Ahmadi
2ce9c3cc81 fix core restart on client change (#2603) 2024-11-16 17:05:23 +03:30
Azoo224
0a8bfc2725 Bash menu: "No Such File or Directory" While Installing Certs (#2602) 2024-11-16 15:02:16 +03:30
modimobeikete
c5bbb6b632 Add new language ja-JP (#2601) 2024-11-15 13:38:48 +03:30
mhsanaei
f497a8dbcc v2.4.7 2024-11-14 19:36:10 +03:30
mhsanaei
4290081486 readme: postman
idk why the old link has been removed
2024-11-14 19:21:23 +03:30
mhsanaei
ccda652e69 SplitHTTP - Mode 2024-11-14 13:09:51 +03:30
mhsanaei
2982d809ab update - CF SSL Certificate 2024-11-14 12:00:24 +03:30
mhsanaei
7ad4a3dffc Xray-core v24.11.11 2024-11-13 14:23:53 +03:30
mhsanaei
c0ef53e542 Access URL - SSL exist 2024-11-12 15:39:23 +03:30
mhsanaei
b7d1c84cd0 README OS - Windows x64 2024-11-12 14:26:35 +03:30
mhsanaei
111bfe5d2e ShadowSocks - ivCheck 2024-11-12 14:01:42 +03:30
mhsanaei
6e59aa14b0 Xray core buggy version removed
accept >= v24.11.11 or v1.8.24
2024-11-12 12:13:10 +03:30
mhsanaei
a4cf77422f update dependencies 2024-11-12 01:29:03 +03:30
DecorativeFamily
35df2a0505 [CodeFactor] Apply fixes (#2595)
Co-authored-by: codefactor-io <support@codefactor.io>
2024-11-12 01:28:00 +03:30
dependabot[bot]
9f445686a4 Bump google.golang.org/grpc from 1.67.1 to 1.68.0 (#2597)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.67.1 to 1.68.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.67.1...v1.68.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 01:10:21 +03:30
mhsanaei
0fc935e996 fix update geo + log details fail2ban 2024-11-04 13:16:04 +01:00
mhsanaei
adb08a60cf rename - splithttp to xhttp 2024-11-03 10:51:53 +01:00
dependabot[bot]
e3576e8a85 Bump github.com/shirou/gopsutil/v4 from 4.24.9 to 4.24.10 (#2593)
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.9 to 4.24.10.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.9...v4.24.10)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-02 17:40:14 +01:00
mhsanaei
9c065aed4e Access URL - after you get SSL 2024-10-31 15:02:01 +01:00
mhsanaei
7abb092211 v2.4.6 2024-10-31 09:54:53 +01:00
Sanaei
937bfb4c78 SSH Port Forwarding (#2590)
* getListen & getCert

* SSH Port Forwarding

* fix
2024-10-31 09:53:47 +01:00
mhsanaei
1bcdc54b68 Xray Core v24.10.31 2024-10-31 09:47:39 +01:00
mhsanaei
eb58314c53 bash - remove version limit 2024-10-31 01:18:37 +01:00
mhsanaei
c158e6ec73 getListen & getCert 2024-10-31 01:10:17 +01:00
mhsanaei
5ae587ee81 bash - set or reset listenIP
we need this if we want to add SSH port forwarding
2024-10-30 16:35:36 +01:00
mhsanaei
d40fa46851 fix access url 2024-10-30 15:24:18 +01:00
mhsanaei
19a31686da REALITY: SplitHTTP transport 2024-10-30 14:46:27 +01:00
mhsanaei
13f7e07128 bash - Access URL
I will add https with domain later
2024-10-29 15:20:38 +01:00
mhsanaei
0e3691fdbd Xray core buggy version removed
only v24.10.16 or newer and v1.8.24
2024-10-29 14:56:30 +01:00
mhsanaei
e359b5c75e removed - XTLS Security
because its too old and no one use it anymore
2024-10-29 12:50:25 +01:00
pr3ci0u5
3b3bd3dea4 Update translate.ru_RU.toml (#2588) 2024-10-29 11:03:15 +01:00
mhsanaei
8f36b7ea84 update dependencies 2024-10-29 11:00:07 +01:00
mhsanaei
569d99512c iplimit - accept all email format 2024-10-28 20:13:42 +01:00
mhsanaei
ac84553a68 Fail2ban - Real-Time logs 2024-10-28 19:24:44 +01:00
mhsanaei
610db7827d Update install.sh 2024-10-25 11:30:44 +02:00
mhsanaei
088b55c9ed readme 2024-10-24 21:23:21 +02:00
mhsanaei
c800e29900 Update docker.yml 2024-10-24 21:22:40 +02:00
mhsanaei
14435db0d8 bash - Default credentials detected. Security update required 2024-10-24 21:17:00 +02:00
mhsanaei
bd6402562e OS - Alpine Linux
fix bash menu for docker
2024-10-24 20:36:12 +02:00
mhsanaei
d16ad11136 fix outbound noises
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-10-21 10:40:29 +02:00
MHSanaei
6c27e4177d readme: install old version 2024-10-20 14:18:50 +02:00
MHSanaei
bebf83f06c wireguard - noKernelTun 2024-10-20 14:07:21 +02:00
Amazing Watermelon
07bf741b15 Fixed the traditional Chinese name 2024-10-20 11:33:50 +02:00
mhsanaei
5e5851029d update README: SSL Certificate Management 2024-10-17 12:58:18 +02:00
mhsanaei
e6020850fc v2.4.5 2024-10-17 12:27:13 +02:00
mhsanaei
80183f61e8 update README: using old versions
i don't care anymore do whatever you want

Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-10-17 11:45:22 +02:00
mhsanaei
1b9432ff37 move security domains into protection shield 2024-10-17 11:27:36 +02:00
MadrinX
f1f813269c feat(tgbot): Add the option to change the telegram API server (#2584) 2024-10-17 10:59:42 +02:00
mhsanaei
a23f390402 splithttp - xmux (change default value) 2024-10-17 10:40:56 +02:00
mhsanaei
2950ce0c17 freedom - default settings 2024-10-17 10:36:05 +02:00
mhsanaei
514c4909a4 revert changes 2024-10-17 10:34:30 +02:00
mhsanaei
99cadf7652 Update install.sh 2024-10-16 16:29:20 +02:00
mhsanaei
4ca36d64a8 update dependencies 2024-10-16 16:26:07 +02:00
mhsanaei
863009dcaa Refactor size formatting for readability 2024-10-16 16:03:00 +02:00
Ahmad Thoriq Najahi
2ef5ccc2fd feat(tgbot): Allow restart core via telegram bot (#2581) 2024-10-16 14:39:25 +02:00
mhsanaei
744583b4e7 install.sh - check existing settings 2024-10-16 13:49:56 +02:00
mhsanaei
b36032e22c REALITY: target as an alias of dest
I don't want to add it separately because they are the same, just the name is different.
2024-10-16 12:22:07 +02:00
mhsanaei
ac7901abba Wireguard - kernel Tun 2024-10-16 12:08:01 +02:00
mhsanaei
dff2496d73 Xray Core v24.10.16 2024-10-16 12:08:01 +02:00
MHSanaei
d97d36bb9e 500 rows for log page 2024-10-15 21:49:36 +02:00
MHSanaei
b0d2cb93e1 bash menu - debug log, clear all logs 2024-10-15 21:37:14 +02:00
laperuz92
f98d78c356 Update Russian translation. (#2583)
* Update Russian translations.

* Some more fixes
2024-10-15 20:56:09 +02:00
dependabot[bot]
d85226dc79 Bump github.com/nicksnyder/go-i18n/v2 from 2.4.0 to 2.4.1 (#2582)
Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/nicksnyder/go-i18n/releases)
- [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.4.0...v2.4.1)

---
updated-dependencies:
- dependency-name: github.com/nicksnyder/go-i18n/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 20:55:16 +02:00
MHSanaei
1c2b6095c9 fix core restart on traffic reset of disabled client
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-10-15 20:54:23 +02:00
MHSanaei
5f8c8f4525 v2.4.4 2024-10-11 10:19:53 +02:00
MHSanaei
6a49e99a2c update dependencies 2024-10-11 09:42:54 +02:00
MHSanaei
6062031de4 fix lang + more geoip option 2024-10-11 09:15:36 +02:00
Alireza Ahmadi
fb2b58110d fix subJson direct rules (#2580) 2024-10-10 22:33:44 +02:00
Alireza Ahmadi
c385662783 simplified basic routing (#2579) 2024-10-10 21:38:56 +02:00
Sanaei
4a05188a7f Merge pull request #2577 from MHSanaei/new-fixes
New fixes
2024-10-10 21:37:54 +02:00
Alireza Ahmadi
1454c4ebc5 [bug] fix restarting core on disabling depleted user 2024-10-10 17:37:06 +02:00
Alireza Ahmadi
4b1c76e972 [refactor] email verification method 2024-10-10 17:32:50 +02:00
mhsanaei
f1f5d323e8 Sniffing - change default 2024-10-09 12:31:11 +02:00
mhsanaei
e37f2d3222 Username & Password will be generated randomly 2024-10-09 11:18:09 +02:00
mhsanaei
4f2f855c04 validate Email - non-English Pattern 2024-10-09 10:49:05 +02:00
mhsanaei
dcab4e6f9c fix oracle 2024-10-08 16:42:47 +02:00
mhsanaei
e703055793 bash - restart panel after cert set 2024-10-07 17:42:31 +02:00
mhsanaei
7efe1d60d5 readme 2024-10-07 17:34:08 +02:00
mhsanaei
1d84284b6d Update docker.yml 2024-10-07 17:33:55 +02:00
mhsanaei
8404b33232 bash - minor change 2024-10-07 17:33:43 +02:00
mhsanaei
41d39dfaa8 Add user choice for geo updates 2024-10-07 15:49:36 +02:00
mhsanaei
761eb5f384 update install_acme 2024-10-07 15:28:00 +02:00
mhsanaei
e72f67ca54 update dependencies 2024-10-07 15:24:35 +02:00
mhsanaei
b8df15171e ask auto set after get new cert 2024-10-07 15:24:15 +02:00
mhsanaei
5f531f2de1 Set Cert paths for the panel
Option to automatically set the web certificate and key file paths for the panel
2024-10-07 15:13:38 +02:00
mhsanaei
8335238eb3 Auto-detect existing SSL domains 2024-10-07 14:50:59 +02:00
mhsanaei
aaf68ecb21 update api doc 2024-10-07 14:02:51 +02:00
mhsanaei
db6781f311 Update release.yml 2024-10-07 10:35:22 +02:00
bnam999
a85b02c6ab support for openEuler (#2574) 2024-10-07 10:34:26 +02:00
mhsanaei
19a832cad8 change name H2 to HTTP
because we also have h3 on it
2024-10-04 16:34:50 +02:00
mhsanaei
f0dd6152fd if webBasePath lt 3 will be replace with new random 2024-10-04 16:05:45 +02:00
mhsanaei
decac2ef74 Add version check (min v2.3.5) 2024-10-04 15:00:42 +02:00
mhsanaei
c3ce1da0d6 Web base path will be generated randomly 2024-10-04 13:41:52 +02:00
mhsanaei
02bc488c1c go.mod for Xray v24.9.30 2024-10-03 16:06:25 +02:00
mhsanaei
b22f859c38 Show tcpNoDelay only when tcpMptcp is true 2024-10-03 10:52:08 +02:00
mhsanaei
98869c7169 Show buildChain only when usage is 'issue' 2024-10-03 10:48:01 +02:00
mhsanaei
150c89db72 maxConcurrency/maxConnections visibility 2024-10-03 10:43:33 +02:00
Huang Kuan Wei
fe0a8375a3 add language zh-Tw (#2572)
* add translation zh-TW

* add zh-TW to langs.js
2024-10-03 09:46:15 +02:00
mhsanaei
80cfbefd75 v2.4.3 2024-10-01 09:41:12 +02:00
mhsanaei
f2ee18235f update dependencies 2024-10-01 09:40:03 +02:00
mhsanaei
f48df1e5c0 Xray Core v24.9.30 2024-10-01 09:35:06 +02:00
mhsanaei
b24855082e RAW as an alias of TCP 2024-10-01 09:34:13 +02:00
Vyacheslav Scherbinin
27434f3235 Fix toasts (#2571)
* Add space to toast status messages

* Removed opening space from translations
2024-09-30 17:06:38 +02:00
mhsanaei
cdb6eac0e6 Reality - min,max client (not active) 2024-09-27 14:57:04 +02:00
mhsanaei
9e13513205 Temporarily disabled Allocate 2024-09-27 14:37:57 +02:00
mhsanaei
b09f52357c update block and direct connection 2024-09-27 13:34:12 +02:00
mhsanaei
ac08e86747 inbound - better view 2024-09-26 16:20:35 +02:00
mhsanaei
c9c8abe97b refactor: split XTLS and Reality form tls into separate files 2024-09-26 16:01:58 +02:00
mhsanaei
0b8beafc89 DNS - Expect IPs 2024-09-26 13:08:54 +02:00
mhsanaei
8b6e3491c4 base install for amzn 2024-09-26 12:19:18 +02:00
dependabot[bot]
6cf2b56f9b Bump github.com/valyala/fasthttp from 1.55.0 to 1.56.0 (#2566)
Bumps [github.com/valyala/fasthttp](https://github.com/valyala/fasthttp) from 1.55.0 to 1.56.0.
- [Release notes](https://github.com/valyala/fasthttp/releases)
- [Commits](https://github.com/valyala/fasthttp/compare/v1.55.0...v1.56.0)

---
updated-dependencies:
- dependency-name: github.com/valyala/fasthttp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 13:07:10 +02:00
mhsanaei
19b95829e0 OS Support - AlmaLinux 8.0 + 2024-09-25 11:31:07 +02:00
mhsanaei
4bea427c79 minor change 2024-09-25 10:40:21 +02:00
Sanaei
da7e4d51d6 OS Support - Amazon Linux + AlmaLinux 8 2024-09-25 10:13:54 +02:00
mhsanaei
43713fbdf8 v2.4.2 2024-09-24 15:15:29 +02:00
mhsanaei
dc29e858c9 Xray Core v24.9.19 2024-09-24 15:15:19 +02:00
mhsanaei
c30c6f08f3 Direct Sub Json - geoIP, geoSite 2024-09-24 15:11:16 +02:00
mhsanaei
7c892ac051 Iplimit - warning improved 2024-09-24 13:24:10 +02:00
mhsanaei
75dd7b93f5 update dependencies 2024-09-24 12:19:08 +02:00
mhsanaei
d5de8e1bf3 removeNoise - hide when only one noise 2024-09-24 12:18:42 +02:00
mhsanaei
16b4795956 removed - timeout 2024-09-24 12:00:37 +02:00
Pavel Kogen
e5835c299c OS Support - Amazon Linux (#2564) 2024-09-24 11:53:12 +02:00
mhsanaei
4fdef3cfde add or remove noise 2024-09-24 11:38:10 +02:00
dependabot[bot]
cf6a8bd463 Bump google.golang.org/grpc from 1.66.2 to 1.67.0 (#2563)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.66.2 to 1.67.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.66.2...v1.67.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-20 11:45:21 +02:00
mhsanaei
59a84e844c HTTP - Allow Transparent 2024-09-17 11:37:24 +02:00
mhsanaei
6b0c9a5fad update noise to noises
+ type
2024-09-17 09:51:57 +02:00
mhsanaei
77edea5419 v2.4.1 2024-09-16 17:30:53 +02:00
mhsanaei
521870df0a minor changes 2024-09-16 16:49:00 +02:00
mhsanaei
dc1c1eb998 Xray Core v24.9.16 2024-09-16 16:48:44 +02:00
mhsanaei
e78427245a New - splithttp (xmux) 2024-09-16 16:47:59 +02:00
mhsanaei
fbcab5bc52 login page - more width for lang 2024-09-16 13:52:21 +02:00
mhsanaei
89c79c3ec3 more details - vmess security 2024-09-16 13:51:36 +02:00
mhsanaei
566cd9e9c4 New - Allocate 2024-09-16 11:41:21 +02:00
mhsanaei
ad78cec7c7 fix mistake - security only for vmess 2024-09-16 10:37:02 +02:00
mhsanaei
176ab5f48e New - DNS Outbound (nonIPQuery, blockTypes) 2024-09-16 10:30:51 +02:00
laperuz92
9c4fa23931 Some README updates (#2562)
* Update README.ru_RU.md

Fixed some typos here and there

* Add info about docker image autoupdate

* Update Russian translation

* Add info on Portuguese (Brazip) translation
2024-09-13 11:25:57 +02:00
mhsanaei
2938694c45 geosite:category-ru and ir 2024-09-13 11:15:55 +02:00
mhsanaei
88d0fb9753 Reality - maxTimediff 2024-09-13 09:39:57 +02:00
mhsanaei
d23a7f81ef freedom - default value for timeout
I've seen many users make the mistake of not setting the timeout value in Xray, which causes errors. Then, they mistakenly assume that 3x-ui has a bug.
2024-09-13 09:21:37 +02:00
mhsanaei
2e363445fc update dependencies 2024-09-12 11:58:21 +02:00
mhsanaei
33d983bc20 New - maskAddress , dnslog 2024-09-12 11:44:13 +02:00
mhsanaei
3e7c7831bc Email Validation - new pattern
@ included
2024-09-12 10:07:53 +02:00
mhsanaei
374d49eb92 Iplimit - improved
Ensure accurate extraction of email.
Access logs are needed when the IP limit feature is active.
2024-09-12 09:44:17 +02:00
mhsanaei
663cf5649f Session - default 60 minute (minimum) 2024-09-12 09:41:24 +02:00
dependabot[bot]
095ebccbb0 Bump google.golang.org/grpc from 1.66.0 to 1.66.1 (#2558)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.66.0 to 1.66.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.66.0...v1.66.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 11:19:10 +02:00
dependabot[bot]
f4bb6b0517 Bump gorm.io/gorm from 1.25.11 to 1.25.12 (#2543)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.11 to 1.25.12.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.11...v1.25.12)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 17:58:47 +02:00
mhsanaei
575234ac37 v2.4.0 2024-09-09 10:42:20 +02:00
mhsanaei
16cbd86371 update dependencies 2024-09-09 10:35:43 +02:00
mhsanaei
2d3666e339 Xray core v24.9.7 2024-09-09 10:35:39 +02:00
mhsanaei
47987b7785 update readme 2024-09-09 10:31:55 +02:00
mhsanaei
2e1461e6dc remove warning for access log
because you can't see the iplimit when there is no path for access log :D
2024-09-09 09:57:30 +02:00
mhsanaei
272457740f warp script removed
we don't need it
2024-09-09 09:48:48 +02:00
mhsanaei
58c721e7d2 quic removed 2024-09-09 09:46:39 +02:00
mhsanaei
b4baf35ed8 Update Email Validation 2024-09-05 15:16:15 +02:00
mhsanaei
2001d96148 iplimit - ipv6 support 2024-09-05 13:59:30 +02:00
mhsanaei
5c390341fb v2.3.15 2024-09-05 12:06:16 +02:00
mhsanaei
c8b1b162ff update dependencies 2024-09-05 10:21:37 +02:00
mhsanaei
a55645584b new - Português (Brazil) langs 2024-09-05 10:02:32 +02:00
dependabot[bot]
f536307914 Bump github.com/shirou/gopsutil/v4 from 4.24.7 to 4.24.8 (#2508)
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.7 to 4.24.8.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.7...v4.24.8)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 12:02:32 +02:00
Dmitry Zhavoronkov
4494ffabb6 fix TypeError: this.shortIds.split is not a function (#2507)
* fix TypeError: this.shortIds.split is not a function
2024-09-02 12:02:15 +02:00
mhsanaei
2dc59a601c fix restart after enabling user
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-09-02 10:26:19 +02:00
mhsanaei
4ad04e2032 [fragment] manual config for packets
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-09-02 10:24:02 +02:00
mhsanaei
f2ebe128cc freedom redirect
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-09-02 10:23:10 +02:00
mhsanaei
f0165c1ad8 v2.3.14 2024-08-30 10:28:01 +02:00
mhsanaei
898f80cb30 Xray Core v1.8.24 2024-08-30 09:25:49 +02:00
Rizvan Nukhtarov
5d7de0858c better autocomplete for password and token inputs (#2505) 2024-08-30 09:16:34 +02:00
bigbug
c0b88fe736 Nginx reverse proxy setting (#2504)
* Nginx reverse proxy web

Reverse proxy root path or subpath

* Update README.md

* Fix ru_RU doc translation

* Fix translation issues

Fix similar problems with other translations
2024-08-30 09:16:13 +02:00
mhsanaei
fa43248e30 New - Noise
freedom
2024-08-29 11:27:43 +02:00
mhsanaei
cb3da25bc8 bug fix - TLS
disableSystemRoot & enableSessionResumption
2024-08-29 11:06:48 +02:00
dependabot[bot]
a40058bb0b Bump google.golang.org/grpc from 1.65.0 to 1.66.0 (#2502)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.65.0 to 1.66.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.65.0...v1.66.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-29 09:08:14 +02:00
Ilya
6ab3bbe7bd Correct Russian README (#2503)
* Remove trailing whitespace in READMEs

* Correct Russian README and make it more natual

Co-authored-by: Viacheslav64 <74322784+Viacheslav64@users.noreply.github.com>

---------

Co-Authored-By: Viacheslav64 <74322784+Viacheslav64@users.noreply.github.com>
2024-08-29 09:07:42 +02:00
bigbug
9e73c82eb3 Fix Language Code for CN (#2501) 2024-08-28 11:30:49 +02:00
bigbug
6b3b1b6cbc GO v1.23.0 , docker (#2500)
docker build error
2024-08-28 09:15:48 +02:00
mhsanaei
b3b433f84b Русский README 2024-08-27 10:32:06 +02:00
mhsanaei
7f16a53309 GO v1.23.0 + update dependencies 2024-08-27 09:37:07 +02:00
mhsanaei
2471bda211 New - Splithttp (xPaddingBytes) 2024-08-19 00:13:07 +02:00
mhsanaei
cd49971535 update translate for #2493 2024-08-18 23:52:29 +02:00
dependabot[bot]
0013f8989b Bump github.com/mymmrac/telego from 0.31.0 to 0.31.1 (#2492)
Bumps [github.com/mymmrac/telego](https://github.com/mymmrac/telego) from 0.31.0 to 0.31.1.
- [Release notes](https://github.com/mymmrac/telego/releases)
- [Commits](https://github.com/mymmrac/telego/compare/v0.31.0...v0.31.1)

---
updated-dependencies:
- dependency-name: github.com/mymmrac/telego
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-18 23:31:23 +02:00
Rizvan Nukhtarov
de8c80597f New - TGbot, "All clients" button (#2493) 2024-08-18 23:30:56 +02:00
Konstantin Larin
9dcc55ea1b Update Russian translate (#2494) 2024-08-18 23:29:39 +02:00
Matin
8255390131 Update Persian translate (#2495)
* Adjust translates for persian

* Also add spacing for expreIn key

* Adjust spacing agian for expireIn (thanks to vscode for breaking it)
2024-08-18 23:29:10 +02:00
Ilya
438a9684ee Fix #2470 (The subscription service gives two ports for VLESS) (#2479)
* Fix #2470
2024-08-11 18:26:43 +02:00
mhsanaei
a6000f22a2 v2.3.13 2024-08-11 12:43:47 +02:00
mhsanaei
2ec5eeb442 Dokodemo - default timeout to 30s 2024-08-11 12:41:21 +02:00
mhsanaei
d319476eb6 splithttp - change default to accept range (better upload now) 2024-08-11 11:38:59 +02:00
mhsanaei
93d52bc86c new - vmess security (inbound client side - outbound) 2024-08-11 00:47:44 +02:00
mhsanaei
bda5c2c915 small changes 2024-08-08 17:40:26 +02:00
mhsanaei
b838fe2e74 update dependencies 2024-08-08 17:39:50 +02:00
mhsanaei
604b9be4a0 Fix domain validation for Nginx/CDN compatibility #2450 2024-08-08 17:39:12 +02:00
mhsanaei
7b8ef98846 v2.3.12 2024-08-06 21:18:48 +02:00
mhsanaei
2c7c6c260a default setting 2024-08-06 21:17:12 +02:00
mhsanaei
b8c3555b09 improve randomShortId , format document 2024-08-06 17:10:42 +02:00
mhsanaei
2d2b30daf1 update dependencies 2024-08-06 13:45:07 +02:00
mhsanaei
3e3ed4ed52 fix session 2024-08-06 13:44:48 +02:00
mhsanaei
d8d9c64847 new - cert (build Chain) 2024-08-04 00:07:33 +02:00
Atageldi Didarov
c489673130 Added Turkish translation (#2442)
* create translate.tr_TR.toml

* added confirmToggle key

* Update langs.js
2024-08-02 11:32:56 +02:00
dependabot[bot]
2544305fb9 Bump github.com/shirou/gopsutil/v4 from 4.24.6 to 4.24.7 (#2444)
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.6 to 4.24.7.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.6...v4.24.7)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-02 11:20:58 +02:00
Artem
519d228db2 translate improved - RU (#2441) 2024-08-02 11:20:39 +02:00
mhsanaei
4cd89f4379 v2.3.11 2024-07-29 13:17:55 +02:00
mhsanaei
fdfc29f6cd new - splithttp (noSSEHeader) 2024-07-29 13:16:07 +02:00
mhsanaei
4ec104c5ee new - splithttp (scMinPostsIntervalMs) 2024-07-29 13:05:05 +02:00
mhsanaei
bae89272b0 Xray Core v1.8.23 2024-07-29 12:33:46 +02:00
mhsanaei
8408a45eff update dependencies 2024-07-27 14:56:37 +02:00
mhsanaei
a37b1bde4c update pictures 2024-07-24 13:33:36 +02:00
mhsanaei
953b5d3dea buy me a coffee 2024-07-24 12:47:35 +02:00
mhsanaei
c7906e8598 API - Get client's traffic By ID
Co-Authored-By: Hassan Ali Gilani <mr.ajaxian@gmail.com>
2024-07-23 11:28:28 +02:00
yeer
e1bc43da5f fix bug for nil pointer (#2438) 2024-07-23 11:11:28 +02:00
mhsanaei
0630642849 v2.3.10 2024-07-22 00:14:48 +02:00
mhsanaei
8bd3827b41 Xray Core v1.8.21 2024-07-22 00:12:58 +02:00
mhsanaei
24b367b82f fix 2024-07-18 23:03:48 +02:00
mhsanaei
011443bfc1 update donate wallet 2024-07-17 18:13:12 +02:00
mhsanaei
7417c52c8f fixed - sub show time when "Start After First Use" 2024-07-17 16:39:53 +02:00
mhsanaei
4d4eef8d8a Xray Core v1.8.19 2024-07-17 16:21:03 +02:00
mhsanaei
9de8b4acaf v2.3.9 2024-07-17 13:51:11 +02:00
mhsanaei
f21c293693 Xray core v1.8.18 2024-07-16 01:33:24 +02:00
mhsanaei
56159d9c52 Upload files to Artifacts 2024-07-15 11:09:24 +02:00
mhsanaei
6cbdf64013 update dependencies 2024-07-15 10:28:39 +02:00
mhsanaei
bb9b9100a8 [warp] enhanced + delete option
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-15 00:21:54 +02:00
mhsanaei
816adfc3ea fallback outbound in balancer
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-15 00:09:31 +02:00
mhsanaei
0a95b0c7b2 disable mux for vision flow
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-14 23:55:56 +02:00
mhsanaei
d298f4ffbd fix domain validator
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-14 23:55:04 +02:00
mhsanaei
315e8af025 fix observatory data
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-14 23:44:31 +02:00
mhsanaei
de985263f5 safe login
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-14 23:37:43 +02:00
mhsanaei
dfe0bbd371 Refactor database initialization 2024-07-14 01:22:51 +02:00
mhsanaei
60cb328698 default - alpn h3, utls random 2024-07-14 00:03:07 +02:00
mhsanaei
3d7f13225a Refactor HttpUtil and Msg: optimize methods 2024-07-14 00:01:13 +02:00
mhsanaei
76fdfb2ef2 date format - jalalian 2024-07-13 01:38:51 +02:00
mhsanaei
8018023187 Xray core v1.8.17 + GO v1.22.5 2024-07-12 21:42:32 +02:00
mmmray
ea9d5dc2d5 fix wrong splithttp default (#2433)
This default is defined as 1MB, but maxUploadSize is to be specified in
bytes. This confusion could've come from poorly written documentation in
xray, but it has been updated.

in general I wish that panels would not set defaults at all and instead
just omit parameters (in sharelinks, inbounds, ...) that the user didn't
set explicitly. If I want to change the defaults in xray's codebase, it
seems that all the panels will have to update the default too.

I see marzban doing the same kind of things.
2024-07-09 15:24:06 +02:00
mhsanaei
96e43fa195 v2.3.8 2024-07-08 23:48:01 +02:00
mhsanaei
f1500a5d31 improved - message logs 2024-07-08 23:47:49 +02:00
mhsanaei
c9a218d060 axios v1.7.2 2024-07-08 21:03:20 +02:00
mhsanaei
6d18a15c4e Expand arch support in downloadXRay 2024-07-08 18:24:07 +02:00
dependabot[bot]
c0f86d2f38 Bump github.com/mymmrac/telego from 0.30.2 to 0.31.0 (#2432)
Bumps [github.com/mymmrac/telego](https://github.com/mymmrac/telego) from 0.30.2 to 0.31.0.
- [Release notes](https://github.com/mymmrac/telego/releases)
- [Commits](https://github.com/mymmrac/telego/compare/v0.30.2...v0.31.0)

---
updated-dependencies:
- dependency-name: github.com/mymmrac/telego
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 15:48:14 +02:00
mhsanaei
5227fefaeb update dependencies 2024-07-07 12:10:47 +02:00
mhsanaei
7a51d2f2cc Typo fixed 2024-07-07 12:10:24 +02:00
mhsanaei
02ae61fe6b change session name 2024-07-05 14:33:04 +02:00
mhsanaei
24b9e5bfa3 some changes 2024-07-04 15:04:04 +02:00
mhsanaei
9ff7f14b6e unnecessary log 2024-07-04 00:28:37 +02:00
mhsanaei
c3b42b8ea4 typo 2024-07-04 00:17:44 +02:00
mhsanaei
5afb8d85fc Optimize XrayAPI functionality and structure 2024-07-04 00:17:28 +02:00
dependabot[bot]
767ee4ec2b Bump google.golang.org/grpc from 1.64.0 to 1.65.0 (#2431)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.65.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.65.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 23:55:09 +02:00
mhsanaei
21b64beb96 tgbot - login notify (show password for failed login) 2024-07-03 21:53:45 +02:00
mhsanaei
b84e3ef338 improve bash menu 2024-07-02 00:34:25 +02:00
mhsanaei
86586b7e8f v2.3.7 2024-07-01 23:54:12 +02:00
mhsanaei
f9792632d4 pageSize default to 50 2024-07-01 21:11:42 +02:00
mhsanaei
a9ec24f811 update dependencies 2024-07-01 19:45:51 +02:00
mhsanaei
2da7dda794 grpc.Dial is deprecated: use NewClient instead 2024-07-01 19:22:35 +02:00
mhsanaei
39aae6fd16 sub - add hour for time left
1D,10H
22M
2024-07-01 19:11:40 +02:00
dependabot[bot]
f355ab5758 Bump github.com/shirou/gopsutil/v4 from 4.24.5 to 4.24.6 (#2429)
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.5 to 4.24.6.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.5...v4.24.6)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 17:13:28 +02:00
mhsanaei
3847bc0a78 Update README.md 2024-06-27 12:45:58 +02:00
mhsanaei
546d676472 Xray Core v1.8.16 2024-06-25 08:56:52 +02:00
mhsanaei
6fb6241c3c bash - update menu 2024-06-24 15:45:50 +02:00
mhsanaei
f481ab993e bash - Reset Web Base Path 2024-06-24 15:06:52 +02:00
mhsanaei
3ef4ab423f Update README.md 2024-06-24 14:15:32 +02:00
mhsanaei
b5a32ef57e update commands 2024-06-24 13:47:13 +02:00
mhsanaei
4033001798 update readme 2024-06-24 12:36:38 +02:00
mhsanaei
2486b5ff43 ensure file exists 2024-06-24 09:57:46 +02:00
dependabot[bot]
58647c6496 Bump github.com/xtls/xray-core from 1.8.15 to 1.8.16 (#2426)
Bumps [github.com/xtls/xray-core](https://github.com/xtls/xray-core) from 1.8.15 to 1.8.16.
- [Release notes](https://github.com/xtls/xray-core/releases)
- [Commits](https://github.com/xtls/xray-core/compare/v1.8.15...v1.8.16)

---
updated-dependencies:
- dependency-name: github.com/xtls/xray-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 00:57:32 +02:00
263 changed files with 27532 additions and 88212 deletions

14
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
# These are supported funding model platforms
github: MHSanaei
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: mhsanaei
custom: https://nowpayments.io/donation/hsanaei

View File

@@ -1,24 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version (please complete the following information):**
- 3X-UI Version : [e.g. 2.3.5]
- Xray Version : [e.g. 1.8.13]
**Additional context**
Add any other context about the problem here.

77
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: Bug report
description: Create a report to help us improve
title: "Bug report"
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thank you for reporting a bug! Please fill out the following information.
- type: textarea
id: what-happened
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
placeholder: My problem is...
validations:
required: true
- type: textarea
id: how-repeat-problem
attributes:
label: How to repeat the problem?
description: Sequence of actions that allow you to reproduce the bug
placeholder: |
1. Open `Inbounds` page
2. ...
validations:
required: true
- type: textarea
id: expected-action
attributes:
label: Expected action
description: What's going to happen
placeholder: Must be...
validations:
required: false
- type: textarea
id: received-action
attributes:
label: Received action
description: What's really happening
placeholder: It's actually happening...
validations:
required: false
- type: input
id: xui-version
attributes:
label: 3x-ui Version
description: Which version of 3x-ui are you using?
placeholder: 2.X.X
validations:
required: true
- type: input
id: xray-version
attributes:
label: Xray-core Version
description: Which version of Xray-core are you using?
placeholder: 2.X.X
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Please check all the checkboxes
options:
- label: This bug report is written entirely in English.
required: true
- label: This bug report is new and no one has reported it before me.
required: true

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,39 @@
name: Feature request
description: Suggest an idea for this project
title: "Feature request"
labels: ["enhancement"]
body:
- type: textarea
id: is-related-problem
attributes:
label: Is your feature request related to a problem?
description: A clear and concise description of what the problem is.
placeholder: I'm always frustrated when...
validations:
required: true
- type: textarea
id: solution
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Please check all the checkboxes
options:
- label: This feature report is written entirely in English.
required: true

View File

@@ -1,10 +0,0 @@
---
name: 'Question '
about: Describe this issue template's purpose here.
title: ''
labels: question
assignees: ''
---

22
.github/ISSUE_TEMPLATE/question.yaml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Question
description: Describe this issue template's purpose here.
title: "Question"
labels: ["question"]
body:
- type: textarea
id: question
attributes:
label: Question
placeholder: I have a question, ..., how can I solve it?
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: Checklist
description: Please check all the checkboxes
options:
- label: This question is written entirely in English.
required: true

View File

@@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

20
.github/pull_request_template.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
## What is the pull request?
<!-- Briefly describe the changes introduced by this pull request -->
## Which part of the application is affected by the change?
- [ ] Frontend
- [ ] Backend
## Type of Changes
- [ ] Bug fix
- [ ] New feature
- [ ] Refactoring
- [ ] Other
## Screenshots
<!-- Add screenshots to illustrate the changes -->
<!-- Remove this section if it is not applicable. -->

View File

@@ -1,22 +1,49 @@
name: Release 3X-UI for Docker
permissions:
contents: read
packages: write
on:
workflow_dispatch:
push:
tags:
- "*"
workflow_dispatch:
- "v*.*.*"
jobs:
build_and_push:
build:
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: true
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
hsanaeii/3x-ui
ghcr.io/mhsanaei/3x-ui
tags: |
type=ref,event=branch
type=ref,event=tag
type=semver,pattern={{version}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
install: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@v3
@@ -25,17 +52,11 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
push: true
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6,linux/386
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,13 +1,28 @@
name: Release 3X-UI
on:
push:
tags:
- "*"
workflow_dispatch:
release:
types: [published]
push:
branches:
- main
tags:
- "v*.*.*"
paths:
- '**.js'
- '**.css'
- '**.html'
- '**.sh'
- '**.go'
- 'go.mod'
- 'go.sum'
- 'x-ui.service'
jobs:
build:
permissions:
contents: write
strategy:
matrix:
platform:
@@ -18,61 +33,48 @@ jobs:
- 386
- armv5
- s390x
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.22'
go-version-file: go.mod
check-latest: true
- name: Install dependencies
run: |
sudo apt-get update
if [ "${{ matrix.platform }}" == "arm64" ]; then
sudo apt install gcc-aarch64-linux-gnu
elif [ "${{ matrix.platform }}" == "armv7" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "armv6" ]; then
sudo apt install gcc-arm-linux-gnueabihf
elif [ "${{ matrix.platform }}" == "386" ]; then
sudo apt install gcc-i686-linux-gnu
elif [ "${{ matrix.platform }}" == "armv5" ]; then
sudo apt install gcc-arm-linux-gnueabi
elif [ "${{ matrix.platform }}" == "s390x" ]; then
sudo apt install gcc-s390x-linux-gnu
fi
- name: Build x-ui
- name: Build 3X-UI
run: |
export CGO_ENABLED=1
export GOOS=linux
export GOARCH=${{ matrix.platform }}
if [ "${{ matrix.platform }}" == "arm64" ]; then
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv7" ]; then
export GOARCH=arm
export GOARM=7
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "armv6" ]; then
export GOARCH=arm
export GOARM=6
export CC=arm-linux-gnueabihf-gcc
elif [ "${{ matrix.platform }}" == "386" ]; then
export GOARCH=386
export CC=i686-linux-gnu-gcc
elif [ "${{ matrix.platform }}" == "armv5" ]; then
export GOARCH=arm
export GOARM=5
export CC=arm-linux-gnueabi-gcc
elif [ "${{ matrix.platform }}" == "s390x" ]; then
export GOARCH=s390x
export CC=s390x-linux-gnu-gcc
fi
go build -o xui-release -v main.go
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
case "${{ matrix.platform }}" in
amd64) BOOTLIN_ARCH="x86-64" ;;
arm64) BOOTLIN_ARCH="aarch64" ;;
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
386) BOOTLIN_ARCH="x86-i686" ;;
s390x) BOOTLIN_ARCH="s390x-z13" ;;
esac
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
echo "Downloading: $TARBALL_URL"
cd /tmp
curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
tar -xf "$(basename "$TARBALL_URL")"
TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
cd -
go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go
file xui-release
ldd xui-release || echo "Static binary confirmed"
mkdir x-ui
cp xui-release x-ui/
@@ -83,54 +85,140 @@ jobs:
cd x-ui/bin
# Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.15/"
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.10.15/"
if [ "${{ matrix.platform }}" == "amd64" ]; then
wget ${Xray_URL}Xray-linux-64.zip
wget -q ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip
rm -f Xray-linux-64.zip
elif [ "${{ matrix.platform }}" == "arm64" ]; then
wget ${Xray_URL}Xray-linux-arm64-v8a.zip
wget -q ${Xray_URL}Xray-linux-arm64-v8a.zip
unzip Xray-linux-arm64-v8a.zip
rm -f Xray-linux-arm64-v8a.zip
elif [ "${{ matrix.platform }}" == "armv7" ]; then
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
wget -q ${Xray_URL}Xray-linux-arm32-v7a.zip
unzip Xray-linux-arm32-v7a.zip
rm -f Xray-linux-arm32-v7a.zip
elif [ "${{ matrix.platform }}" == "armv6" ]; then
wget ${Xray_URL}Xray-linux-arm32-v6.zip
wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
unzip Xray-linux-arm32-v6.zip
rm -f Xray-linux-arm32-v6.zip
elif [ "${{ matrix.platform }}" == "386" ]; then
wget ${Xray_URL}Xray-linux-32.zip
wget -q ${Xray_URL}Xray-linux-32.zip
unzip Xray-linux-32.zip
rm -f Xray-linux-32.zip
elif [ "${{ matrix.platform }}" == "armv5" ]; then
wget ${Xray_URL}Xray-linux-arm32-v5.zip
wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
unzip Xray-linux-arm32-v5.zip
rm -f Xray-linux-arm32-v5.zip
elif [ "${{ matrix.platform }}" == "s390x" ]; then
wget ${Xray_URL}Xray-linux-s390x.zip
wget -q ${Xray_URL}Xray-linux-s390x.zip
unzip Xray-linux-s390x.zip
rm -f Xray-linux-s390x.zip
fi
rm -f geoip.dat geosite.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
mv xray xray-linux-${{ matrix.platform }}
cd ../..
- name: Package
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
- name: Upload files to Artifacts
uses: actions/upload-artifact@v4
with:
name: x-ui-linux-${{ matrix.platform }}
path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
- name: Upload files to GH release
uses: svenstaro/upload-release-action@v2
if: |
(github.event_name == 'release' && github.event.action == 'published') ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-linux-${{ matrix.platform }}.tar.gz
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
overwrite: true
prerelease: true
# =================================
# Windows Build
# =================================
build-windows:
name: Build for Windows
permissions:
contents: write
strategy:
matrix:
platform:
- amd64
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- name: Build 3X-UI for Windows
shell: pwsh
run: |
$env:CGO_ENABLED="1"
$env:GOOS="windows"
$env:GOARCH="amd64"
go build -ldflags "-w -s" -o xui-release.exe -v main.go
mkdir x-ui
Copy-Item xui-release.exe x-ui\
mkdir x-ui\bin
cd x-ui\bin
# Download Xray for Windows
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v25.10.15/"
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
Remove-Item "Xray-windows-64.zip"
Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat"
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat"
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat"
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat"
Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip_RU.dat"
Invoke-WebRequest -Uri "https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite_RU.dat"
Rename-Item xray.exe xray-windows-amd64.exe
cd ..
Copy-Item -Path ..\windows_files\* -Destination . -Recurse
cd ..
- name: Package to Zip
shell: pwsh
run: |
Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
- name: Upload files to Artifacts
uses: actions/upload-artifact@v4
with:
name: x-ui-windows-amd64
path: ./x-ui-windows-amd64.zip
- name: Upload files to GH release
uses: svenstaro/upload-release-action@v2
if: |
(github.event_name == 'release' && github.event.action == 'published') ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
file: x-ui-windows-amd64.zip
asset_name: x-ui-windows-amd64.zip
overwrite: true
prerelease: true

40
.gitignore vendored
View File

@@ -1,16 +1,40 @@
.idea
.vscode
.cache
# Ignore editor and IDE settings
.idea/
.vscode/
.cache/
.sync*
*.tar.gz
# Ignore log files
*.log
access.log
error.log
tmp
main
# Ignore temporary files
tmp/
*.tar.gz
# Ignore build and distribution directories
backup/
bin/
dist/
release/
node_modules/
# Ignore compiled binaries
main
# Ignore script and executable files
/release.sh
/x-ui
# Ignore OS specific files
.DS_Store
Thumbs.db
# Ignore Go build files
*.exe
x-ui.db
# Ignore Docker specific files
docker-compose.override.yml
# Ignore .env (Environment Variables) file
.env

35
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"$schema": "vscode://schemas/launch",
"version": "0.2.0",
"configurations": [
{
"name": "Run 3x-ui (Debug)",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"cwd": "${workspaceFolder}",
"env": {
"XUI_DEBUG": "true"
},
"console": "integratedTerminal"
},
{
"name": "Run 3x-ui (Debug, custom env)",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"cwd": "${workspaceFolder}",
"env": {
// Set to true to serve assets/templates directly from disk for development
"XUI_DEBUG": "true",
// Uncomment to override DB folder location (by default uses working dir on Windows when debug)
// "XUI_DB_FOLDER": "${workspaceFolder}",
// Example: override log level (debug|info|notice|warn|error)
// "XUI_LOG_LEVEL": "debug"
},
"console": "integratedTerminal"
}
]
}

75
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,75 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "go: build",
"type": "shell",
"command": "go",
"args": [
"build",
"-o",
"bin/3x-ui.exe",
"./main.go"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$go"
],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "go: run",
"type": "shell",
"command": "go",
"args": [
"run",
"./main.go"
],
"options": {
"cwd": "${workspaceFolder}",
"env": {
"XUI_DEBUG": "true"
}
},
"problemMatcher": [
"$go"
]
},
{
"label": "go: test",
"type": "shell",
"command": "go",
"args": [
"test",
"./..."
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$go"
],
"group": "test"
},
{
"label": "go: vet",
"type": "shell",
"command": "go",
"args": [
"vet",
"./..."
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$go"
]
}
]
}

View File

@@ -1,7 +1,7 @@
#!/bin/sh
# Start fail2ban
fail2ban-client -x start
[ $XUI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start
# Run x-ui
exec /app/x-ui

View File

@@ -27,14 +27,14 @@ case $1 in
esac
mkdir -p build/bin
cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.15/Xray-linux-${ARCH}.zip"
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.10.15/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}"
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -O geoip_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geoip.dat
wget -O geosite_VN.dat https://github.com/vuong2023/vn-v2ray-rules/releases/latest/download/geosite.dat
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
cd ../../

View File

@@ -1,7 +1,7 @@
# ========================================================
# Stage: Builder
# ========================================================
FROM golang:1.22-alpine AS builder
FROM golang:1.25-alpine AS builder
WORKDIR /app
ARG TARGETARCH
@@ -15,7 +15,7 @@ COPY . .
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
RUN go build -o build/x-ui main.go
RUN go build -ldflags "-w -s" -o build/x-ui main.go
RUN ./DockerInit.sh "$TARGETARCH"
# ========================================================
@@ -48,6 +48,8 @@ RUN chmod +x \
/app/x-ui \
/usr/bin/x-ui
ENV XUI_ENABLE_FAIL2BAN="true"
EXPOSE 2053
VOLUME [ "/etc/x-ui" ]
CMD [ "./x-ui" ]
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]

56
README.ar_EG.md Normal file
View File

@@ -0,0 +1,56 @@
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
<img alt="3x-ui" src="./media/3x-ui-light.png">
</picture>
</p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v2.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v2)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v2)
**3X-UI** — لوحة تحكم متقدمة مفتوحة المصدر تعتمد على الويب مصممة لإدارة خادم Xray-core. توفر واجهة سهلة الاستخدام لتكوين ومراقبة بروتوكولات VPN والوكيل المختلفة.
> [!IMPORTANT]
> هذا المشروع مخصص للاستخدام الشخصي والاتصال فقط، يرجى عدم استخدامه لأغراض غير قانونية، يرجى عدم استخدامه في بيئة الإنتاج.
كمشروع محسن من مشروع X-UI الأصلي، يوفر 3X-UI استقرارًا محسنًا ودعمًا أوسع للبروتوكولات وميزات إضافية.
## البدء السريع
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
للحصول على الوثائق الكاملة، يرجى زيارة [ويكي المشروع](https://github.com/MHSanaei/3x-ui/wiki).
## شكر خاص إلى
- [alireza0](https://github.com/alireza0/)
## الاعتراف
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (الترخيص: **GPL-3.0**): _قواعد توجيه v2ray/xray و v2ray/xray-clients المحسنة مع النطاقات الإيرانية المدمجة وتركيز على الأمان وحظر الإعلانات._
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (الترخيص: **GPL-3.0**): _يحتوي هذا المستودع على قواعد توجيه V2Ray محدثة تلقائيًا بناءً على بيانات النطاقات والعناوين المحظورة في روسيا._
## دعم المشروع
**إذا كان هذا المشروع مفيدًا لك، فقد ترغب في إعطائه**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## النجوم عبر الزمن
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View File

@@ -1,475 +1,57 @@
# 3X-UI
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
<img alt="3x-ui" src="./media/3x-ui-light.png">
</picture>
</p>
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
**Un Panel Web Avanzado • Construido sobre Xray Core**
[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#)
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v2.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v2)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v2)
> **Descargo de responsabilidad:** Este proyecto es solo para aprendizaje personal y comunicación, por favor no lo uses con fines ilegales, por favor no lo uses en un entorno de producción
**3X-UI** — panel de control avanzado basado en web de código abierto diseñado para gestionar el servidor Xray-core. Ofrece una interfaz fácil de usar para configurar y monitorear varios protocolos VPN y proxy.
**Si este proyecto te es útil, podrías considerar darle una**:star2:
> [!IMPORTANT]
> Este proyecto es solo para uso personal y comunicación, por favor no lo use para fines ilegales, por favor no lo use en un entorno de producción.
<p align="left"><a href="#"><img width="125" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1" alt="Image"></a></p>
Como una versión mejorada del proyecto X-UI original, 3X-UI proporciona mayor estabilidad, soporte más amplio de protocolos y características adicionales.
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
## Instalar y Actualizar
## Inicio Rápido
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
## Instalar una Versión Personalizada
Para documentación completa, visita la [Wiki del proyecto](https://github.com/MHSanaei/3x-ui/wiki).
Para instalar la versión deseada, agrega la versión al final del comando de instalación. Por ejemplo, ver `v2.3.6`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.6
```
## Certificado SSL
<details>
<summary>Haz clic para el Certificado SSL</summary>
### Cloudflare
El script de gestión tiene una aplicación de certificado SSL incorporada para Cloudflare. Para usar este script para colocar un certificado, necesitas lo siguiente:
- Correo electrónico registrado en Cloudflare
- Clave Global de API de Cloudflare
- El nombre de dominio se ha resuelto en el servidor actual a través de Cloudflare
**1:** Ejecuta el comando`x-ui`en la terminal, luego elige `Certificado SSL de Cloudflare`.
### Certbot
```
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
***Consejo:*** *Certbot también está integrado en el script de gestión. Puedes ejecutar el comando `x-ui` , luego elegir `Gestión de Certificados SSL`.*
</details>
## Instalación y Actualización Manual
<details>
<summary>Haz clic para más detalles de la instalación manual</summary>
#### Uso
1. Para descargar la última versión del paquete comprimido directamente en tu servidor, ejecuta el siguiente comando:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
```
2. Una vez que se haya descargado el paquete comprimido, ejecuta los siguientes comandos para instalar o actualizar x-ui:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
cp x-ui/x-ui.sh /usr/bin/x-ui
cp -f x-ui/x-ui.service /etc/systemd/system/
mv x-ui/ /usr/local/
systemctl daemon-reload
systemctl enable x-ui
systemctl restart x-ui
```
</details>
## Instalar con Docker
<details>
<summary>Haz clic para más detalles del Docker</summary>
#### Uso
1. Instala Docker:
```sh
bash <(curl -sSL https://get.docker.com)
```
2. Clona el Repositorio del Proyecto:
```sh
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
```
3. Inicia el Servicio
```sh
docker compose up -d
```
O tambien
```sh
docker run -itd \
-e XRAY_VMESS_AEAD_FORCED=false \
-v $PWD/db/:/etc/x-ui/ \
-v $PWD/cert/:/root/cert/ \
--network=host \
--restart=unless-stopped \
--name 3x-ui \
ghcr.io/mhsanaei/3x-ui:latest
```
actualizar a la última versión
```sh
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
```
eliminar 3x-ui de docker
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details>
## SO Recomendados
- Ubuntu 20.04+
- Debian 11+
- CentOS 8+
- Fedora 36+
- Arch Linux
- Manjaro
- Armbian
- AlmaLinux 9+
- Rockylinux 9+
- OpenSUSE Tubleweed
## Arquitecturas y Dispositivos Compatibles
<details>
<summary>Haz clic para detalles de arquitecturas y dispositivos compatibles</summary>
Nuestra plataforma ofrece compatibilidad con una amplia gama de arquitecturas y dispositivos, garantizando flexibilidad en diversos entornos informáticos. A continuación se presentan las principales arquitecturas que admitimos:
- **amd64**: Esta arquitectura predominante es la estándar para computadoras personales y servidores, y admite la mayoría de los sistemas operativos modernos sin problemas.
- **x86 / i386**: Ampliamente adoptada en computadoras de escritorio y portátiles, esta arquitectura cuenta con un amplio soporte de numerosos sistemas operativos y aplicaciones, incluidos, entre otros, Windows, macOS y sistemas Linux.
- **armv8 / arm64 / aarch64**: Diseñada para dispositivos móviles y embebidos contemporáneos, como teléfonos inteligentes y tabletas, esta arquitectura está ejemplificada por dispositivos como Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, entre otros.
- **armv7 / arm / arm32**: Sirve como arquitectura para dispositivos móviles y embebidos más antiguos, y sigue siendo ampliamente utilizada en dispositivos como Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, entre otros.
- **armv6 / arm / arm32**: Orientada a dispositivos embebidos muy antiguos, esta arquitectura, aunque menos común, todavía se utiliza. Dispositivos como Raspberry Pi 1, Raspberry Pi Zero/Zero W, dependen de esta arquitectura.
- **armv5 / arm / arm32**: Una arquitectura más antigua asociada principalmente con sistemas embebidos tempranos, es menos común hoy en día pero aún puede encontrarse en dispositivos heredados como versiones antiguas de Raspberry Pi y algunos teléfonos inteligentes más antiguos.
</details>
## Idiomas
- Inglés
- Farsi
- Chino
- Ruso
- Vietnamita
- Español
- Indonesio
- Ucraniano
## Características
- Monitoreo del Estado del Sistema
- Búsqueda dentro de todas las reglas de entrada y clientes
- Tema Oscuro/Claro
- Soporta multiusuario y multiprotocolo
- Soporta protocolos, incluyendo VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, Socks, HTTP, wireguard
- Soporta Protocolos nativos XTLS, incluyendo RPRX-Direct, Visión, REALITY
- Estadísticas de tráfico, límite de tráfico, límite de tiempo de vencimiento
- Plantillas de configuración de Xray personalizables
- Soporta acceso HTTPS al panel (dominio proporcionado por uno mismo + certificado SSL)
- Soporta la solicitud y renovación automática de certificados SSL con un clic
- Para elementos de configuración más avanzados, consulta el panel
- Corrige rutas de API (la configuración del usuario se creará con la API)
- Soporta cambiar las configuraciones por diferentes elementos proporcionados en el panel.
- Soporta exportar/importar base de datos desde el panel
## Configuraciones por Defecto
<details>
<summary>Haz clic para detalles de las configuraciones por defecto</summary>
### Información
- **Puerto:** 2053
- **Usuario y Contraseña:** Se generarán aleatoriamente si omites la modificación.
- **Ruta de la Base de Datos:**
- /etc/x-ui/x-ui.db
- **Ruta de Configuración de Xray:**
- /usr/local/x-ui/bin/config.json
- **Ruta del Panel Web sin Implementar SSL:**
- http://ip:2053/panel
- http://domain:2053/panel
- **Ruta del Panel Web con Implementación de SSL:**
- https://domain:2053/panel
</details>
## Configuración WARP
<details>
<summary>Haz clic para detalles de la configuración WARP</summary>
#### Uso
Si deseas usar enrutamiento a WARP antes de la versión v2.1.0, sigue los pasos a continuación:
**1.** Instala WARP en **Modo de Proxy SOCKS**:
```sh
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
```
**2.** Si ya instalaste warp, puedes desinstalarlo usando el siguiente comando:
```sh
warp u
```
**3.** Activa la configuración que necesites en el panel
Características de Configuración:
- Bloquear Anuncios
- Enrutar Google + Netflix + Spotify + OpenAI (ChatGPT) a WARP
- Corregir error 403 de Google
</details>
## Límite de IP
<details>
<summary>Haz clic para más detalles del límite de IP</summary>
#### Uso
**Nota:** El Límite de IP no funcionará correctamente cuando se use IP Tunnel
- Para versiones hasta `v1.6.1`:
- El límite de IP está integrado en el panel.
- Para versiones `v1.7.0` y posteriores:
- Para que el Límite de IP funcione correctamente, necesitas instalar fail2ban y sus archivos requeridos siguiendo estos pasos:
1. Usa el comando `x-ui` dentro de la terminal.
2. Selecciona `Gestión de Límite de IP`.
3. Elige las opciones apropiadas según tus necesidades.
- asegúrate de tener ./access.log en tu Configuración de Xray después de la v2.1.3 tenemos una opción para ello
```sh
"log": {
"access": "./access.log",
"dnsLog": false,
"loglevel": "warning"
},
```
</details>
## Bot de Telegram
<details>
<summary>Haz clic para más detalles del bot de Telegram</summary>
#### Uso
El panel web admite tráfico diario, inicio de sesión en el panel, copia de seguridad de la base de datos, estado del sistema, información del cliente y otras notificaciones y funciones a través del Bot de Telegram. Para usar el bot, debes establecer los parámetros relacionados con el bot en el panel, que incluyen:
- Token de Telegram
- ID de chat de administrador(es)
- Hora de Notificación (en sintaxis cron)
- Notificación de Fecha de Caducidad
- Notificación de Capacidad de Tráfico
- Copia de seguridad de la base de datos
- Notificación de Carga de CPU
**Sintaxis de referencia:**
- `30 \* \* \* \* \*` - Notifica a los 30s de cada punto
- `0 \*/10 \* \* \* \*` - Notifica en el primer segundo de cada 10 minutos
- `@hourly` - Notificación por hora
- `@daily` - Notificación diaria (00:00 de la mañana)
- `@weekly` - Notificación semanal
- `@every 8h` - Notifica cada 8 horas
### Funcionalidades del Bot de Telegram
- Reporte periódico
- Notificación de inicio de sesión
- Notificación de umbral de CPU
- Umbral de Notificación para Fecha de Caducidad y Tráfico para informar con anticipación
- Soporte para menú de reporte de cliente si el nombre de usuario de Telegram del cliente se agrega a las configuraciones de usuario
- Soporte para reporte de tráfico de Telegram buscado con UUID (VMESS/VLESS) o Contraseña (TROJAN) - anónimamente
- Bot basado en menú
- Buscar cliente por correo electrónico (solo administrador)
- Ver todas las Entradas
- Ver estado del servidor
- Ver clientes agotados
- Recibir copia de seguridad bajo demanda y en informes periódicos
- Bot multilingüe
### Configuración del Bot de Telegram
- Inicia [Botfather](https://t.me/BotFather) en tu cuenta de Telegram:
![Botfather](./media/botfather.png)
- Crea un nuevo bot usando el comando /newbot: Te hará 2 preguntas, Un nombre y un nombre de usuario para tu bot. Ten en cuenta que el nombre de usuario debe terminar con la palabra "bot".
![Create new bot](./media/newbot.png)
- Inicia el bot que acabas de crear. Puedes encontrar el enlace a tu bot aquí.
![token](./media/token.png)
- Ingresa a tu panel y configura los ajustes del bot de Telegram como se muestra a continuación:
![Panel Config](./media/panel-bot-config.png)
Ingresa el token de tu bot en el campo de entrada número 3.
Ingresa el ID de chat de usuario en el campo de entrada número 4. Las cuentas de Telegram con esta ID serán los administradores del bot. (Puedes ingresar más de uno, solo sepáralos con ,)
- ¿Cómo obtener el ID de chat de Telegram? Usa este [bot](https://t.me/useridinfobot), Inicia el bot y te dará el ID de chat del usuario de Telegram.
![User ID](./media/user-id.png)
</details>
## Rutas de API
<details>
<summary>Haz clic para más detalles de las rutas de API</summary>
#### Uso
- `/login` con `POST` datos de usuario: `{username: '', password: ''}` para iniciar sesión
- `/panel/api/inbounds` base para las siguientes acciones:
| Método | Ruta | Acción |
| :----: | ---------------------------------- | --------------------------------------------------------- |
| `GET` | `"/list"` | Obtener todas los Entradas |
| `GET` | `"/get/:id"` | Obtener Entrada con inbound.id |
| `GET` | `"/getClientTraffics/:email"` | Obtener Tráficos del Cliente con email |
| `GET` | `"/createbackup"` | El bot de Telegram envía copia de seguridad a los admins |
| `POST` | `"/add"` | Agregar Entrada |
| `POST` | `"/del/:id"` | Eliminar Entrada |
| `POST` | `"/update/:id"` | Actualizar Entrada |
| `POST` | `"/clientIps/:email"` | Dirección IP del Cliente |
| `POST` | `"/clearClientIps/:email"` | Borrar Dirección IP del Cliente |
| `POST` | `"/addClient"` | Agregar Cliente a la Entrada |
| `POST` | `"/:id/delClient/:clientId"` | Eliminar Cliente por clientId\* |
| `POST` | `"/updateClient/:clientId"` | Actualizar Cliente por clientId\* |
| `POST` | `"/:id/resetClientTraffic/:email"` | Restablecer Tráfico del Cliente |
| `POST` | `"/resetAllTraffics"` | Restablecer tráfico de todos las Entradas |
| `POST` | `"/resetAllClientTraffics/:id"` | Restablecer tráfico de todos los clientes en una Entrada |
| `POST` | `"/delDepletedClients/:id"` | Eliminar clientes agotados de la entrada (-1: todos) |
| `POST` | `"/onlines"` | Obtener usuarios en línea (lista de correos electrónicos) |
\*- El campo `clientId` debe llenarse por:
- `client.id` para VMESS y VLESS
- `client.password` para TROJAN
- `client.email` para Shadowsocks
- [Documentación de API](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
</details>
## Variables de Entorno
<details>
<summary>Haz clic para más detalles de las variables de entorno</summary>
#### Uso
| Variable | Tipo | Predeterminado |
| -------------- | :--------------------------------------------: | :------------- |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
Ejemplo:
```sh
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
```
</details>
## Vista previa
![1](./media/1.png)
![2](./media/2.png)
![3](./media/3.png)
![4](./media/4.png)
![5](./media/5.png)
![6](./media/6.png)
![7](./media/7.png)
## Un agradecimiento especial a
## Un Agradecimiento Especial a
- [alireza0](https://github.com/alireza0/)
## Reconocimientos
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Licencia: **GPL-3.0**): _Reglas de enrutamiento mejoradas de v2ray/xray y v2ray/xray-clients con dominios iraníes integrados y un enfoque en seguridad y bloqueo de anuncios._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _Un dominio alojado en Vietnam y una lista de bloqueo con la máxima eficiencia para vietnamitas._
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Licencia: **GPL-3.0**): _Reglas de enrutamiento mejoradas para v2ray/xray y v2ray/xray-clients con dominios iraníes incorporados y un enfoque en seguridad y bloqueo de anuncios._
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Licencia: **GPL-3.0**): _Este repositorio contiene reglas de enrutamiento V2Ray actualizadas automáticamente basadas en datos de dominios y direcciones bloqueadas en Rusia._
## Estrellas a lo largo del tiempo
## Apoyar el Proyecto
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui)
**Si este proyecto te es útil, puedes darle una**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## Estrellas a lo Largo del Tiempo
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

57
README.fa_IR.md Normal file
View File

@@ -0,0 +1,57 @@
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
<img alt="3x-ui" src="./media/3x-ui-light.png">
</picture>
</p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v2.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v2)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v2)
**3X-UI** — یک پنل کنترل پیشرفته مبتنی بر وب با کد باز که برای مدیریت سرور Xray-core طراحی شده است. این پنل یک رابط کاربری آسان برای پیکربندی و نظارت بر پروتکل‌های مختلف VPN و پراکسی ارائه می‌دهد.
> [!IMPORTANT]
> این پروژه فقط برای استفاده شخصی و ارتباطات است، لطفاً از آن برای اهداف غیرقانونی استفاده نکنید، لطفاً از آن در محیط تولید استفاده نکنید.
به عنوان یک نسخه بهبود یافته از پروژه اصلی X-UI، 3X-UI پایداری بهتر، پشتیبانی گسترده‌تر از پروتکل‌ها و ویژگی‌های اضافی را ارائه می‌دهد.
## شروع سریع
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
برای مستندات کامل، لطفاً به [ویکی پروژه](https://github.com/MHSanaei/3x-ui/wiki) مراجعه کنید.
## تشکر ویژه از
- [alireza0](https://github.com/alireza0/)
## قدردانی
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (مجوز: **GPL-3.0**): _قوانین مسیریابی بهبود یافته v2ray/xray و v2ray/xray-clients با دامنه‌های ایرانی داخلی و تمرکز بر امنیت و مسدود کردن تبلیغات._
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (مجوز: **GPL-3.0**): _این مخزن شامل قوانین مسیریابی V2Ray به‌روزرسانی شده خودکار بر اساس داده‌های دامنه‌ها و آدرس‌های مسدود شده در روسیه است._
## پشتیبانی از پروژه
**اگر این پروژه برای شما مفید است، می‌توانید به آن یک**:star2: بدهید
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## ستاره‌ها در طول زمان
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

504
README.md
View File

@@ -1,483 +1,34 @@
# 3X-UI
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
<img alt="3x-ui" src="./media/3x-ui-light.png">
</picture>
</p>
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
**An Advanced Web Panel • Built on Xray Core**
[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#)
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v2.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v2)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v2)
> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment
**3X-UI** — advanced, open-source web-based control panel designed for managing Xray-core server. It offers a user-friendly interface for configuring and monitoring various VPN and proxy protocols.
**If this project is helpful to you, you may wish to give it a**:star2:
> [!IMPORTANT]
> This project is only for personal using, please do not use it for illegal purposes, please do not use it in a production environment.
<p align="left"><a href="#"><img width="125" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1" alt="Image"></a></p>
As an enhanced fork of the original X-UI project, 3X-UI provides improved stability, broader protocol support, and additional features.
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
## Quick Start
## Install & Upgrade
```
```bash
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
## Install Custom Version
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.3.6`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.6
```
## SSL Certificate
<details>
<summary>Click for SSL Certificate</summary>
### Cloudflare
The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
- Cloudflare registered email
- Cloudflare Global API Key
- The domain name has been resolved to the current server through cloudflare
How to get the Cloudflare Global API Key:
1. Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
2. Visit the link https://dash.cloudflare.com/profile/api-tokens
3. Click on View Global API Key (See the screenshot below)
![](media/APIKey1.PNG)
4. You may have to re-authenticate your account. After that, the API Key will be shown (See the screenshot below)\
![](media/APIKey2.png)
When using, just enter `domain name`, `email`, `API KEY`, the diagram is as follows:
![](media/DetailEnter.png)
### Certbot
```
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
</details>
## Manual Install & Upgrade
<details>
<summary>Click for manual install details</summary>
#### Usage
1. To download the latest version of the compressed package directly to your server, run the following command:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
s390x) echo 's390x' ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
```
2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
s390x) echo 's390x' ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
cp x-ui/x-ui.sh /usr/bin/x-ui
cp -f x-ui/x-ui.service /etc/systemd/system/
mv x-ui/ /usr/local/
systemctl daemon-reload
systemctl enable x-ui
systemctl restart x-ui
```
</details>
## Install with Docker
<details>
<summary>Click for Docker details</summary>
#### Usage
1. Install Docker:
```sh
bash <(curl -sSL https://get.docker.com)
```
2. Clone the Project Repository:
```sh
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
```
3. Start the Service
```sh
docker compose up -d
```
OR
```sh
docker run -itd \
-e XRAY_VMESS_AEAD_FORCED=false \
-v $PWD/db/:/etc/x-ui/ \
-v $PWD/cert/:/root/cert/ \
--network=host \
--restart=unless-stopped \
--name 3x-ui \
ghcr.io/mhsanaei/3x-ui:latest
```
update to latest version
```sh
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
```
remove 3x-ui from docker
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details>
## Recommended OS
- Ubuntu 20.04+
- Debian 11+
- CentOS 8+
- Fedora 36+
- Arch Linux
- Parch Linux
- Manjaro
- Armbian
- AlmaLinux 9+
- Rocky Linux 9+
- Oracle Linux 8+
- OpenSUSE Tubleweed
## Supported Architectures and Devices
<details>
<summary>Click for Supported Architectures and devices details</summary>
Our platform offers compatibility with a diverse range of architectures and devices, ensuring flexibility across various computing environments. The following are key architectures that we support:
- **amd64**: This prevalent architecture is the standard for personal computers and servers, accommodating most modern operating systems seamlessly.
- **x86 / i386**: Widely adopted in desktop and laptop computers, this architecture enjoys broad support from numerous operating systems and applications, including but not limited to Windows, macOS, and Linux systems.
- **armv8 / arm64 / aarch64**: Tailored for contemporary mobile and embedded devices, such as smartphones and tablets, this architecture is exemplified by devices like Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, and more.
- **armv7 / arm / arm32**: Serving as the architecture for older mobile and embedded devices, it remains widely utilized in devices like Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, among others.
- **armv6 / arm / arm32**: Geared towards very old embedded devices, this architecture, while less prevalent, is still in use. Devices such as Raspberry Pi 1, Raspberry Pi Zero/Zero W, rely on this architecture.
- **armv5 / arm / arm32**: An older architecture primarily associated with early embedded systems, it is less common today but may still be found in legacy devices like early Raspberry Pi versions and some older smartphones.
- **s390x**: This architecture is commonly used in IBM mainframe computers and offers high performance and reliability for enterprise workloads.
</details>
## Languages
- English
- Farsi
- Chinese
- Russian
- Vietnamese
- Spanish
- Indonesian
- Ukrainian
## Features
- System Status Monitoring
- Search within all inbounds and clients
- Dark/Light theme
- Supports multi-user and multi-protocol
- Supports protocols, including VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, Socks, HTTP, wireguard
- Supports XTLS native Protocols, including RPRX-Direct, Vision, REALITY
- Traffic statistics, traffic limit, expiration time limit
- Customizable Xray configuration templates
- Supports HTTPS access panel (self-provided domain name + SSL certificate)
- Supports One-Click SSL certificate application and automatic renewal
- For more advanced configuration items, please refer to the panel
- Fixes API routes (user setting will be created with API)
- Supports changing configs by different items provided in the panel.
- Supports export/import database from the panel
## Default Settings
<details>
<summary>Click for default settings details</summary>
### Information
- **Port:** 2053
- **Username & Password:** It will be generated randomly if you skip modifying.
- **Database Path:**
- /etc/x-ui/x-ui.db
- **Xray Config Path:**
- /usr/local/x-ui/bin/config.json
- **Web Panel Path w/o Deploying SSL:**
- http://ip:2053/panel
- http://domain:2053/panel
- **Web Panel Path w/ Deploying SSL:**
- https://domain:2053/panel
</details>
## WARP Configuration
<details>
<summary>Click for WARP configuration details</summary>
#### Usage
If you want to use routing to WARP before v2.1.0 follow steps as below:
**1.** Install WARP on **SOCKS Proxy Mode**:
```sh
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
```
**2.** If you already installed warp, you can uninstall using below command:
```sh
warp u
```
**3.** Turn on the config you need in panel
Config Features:
- Block Ads
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
- Fix Google 403 error
</details>
## IP Limit
<details>
<summary>Click for IP limit details</summary>
#### Usage
**Note:** IP Limit won't work correctly when using IP Tunnel
- For versions up to `v1.6.1`:
- IP limit is built-in into the panel.
- For versions `v1.7.0` and newer:
- To make IP Limit work properly, you need to install fail2ban and its required files by following these steps:
1. Use the `x-ui` command inside the shell.
2. Select `IP Limit Management`.
3. Choose the appropriate options based on your needs.
- make sure you have ./access.log on your Xray Configuration after v2.1.3 we have an option for it
```sh
"log": {
"access": "./access.log",
"dnsLog": false,
"loglevel": "warning"
},
```
</details>
## Telegram Bot
<details>
<summary>Click for Telegram bot details</summary>
#### Usage
The web panel supports daily traffic, panel login, database backup, system status, client info, and other notification and functions through the Telegram Bot. To use the bot, you need to set the bot-related parameters in the panel, including:
- Telegram Token
- Admin Chat ID(s)
- Notification Time (in cron syntax)
- Expiration Date Notification
- Traffic Cap Notification
- Database Backup
- CPU Load Notification
**Reference syntax:**
- `30 \* \* \* \* \*` - Notify at the 30s of each point
- `0 \*/10 \* \* \* \*` - Notify at the first second of each 10 minutes
- `@hourly` - Hourly notification
- `@daily` - Daily notification (00:00 in the morning)
- `@weekly` - weekly notification
- `@every 8h` - Notify every 8 hours
### Telegram Bot Features
- Report periodic
- Login notification
- CPU threshold notification
- Threshold for Expiration time and Traffic to report in advance
- Support client report menu if client's telegram username added to the user's configurations
- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
- Menu based bot
- Search client by email ( only admin )
- Check all inbounds
- Check server status
- Check depleted users
- Receive backup by request and in periodic reports
- Multi language bot
### Setting up Telegram bot
- Start [Botfather](https://t.me/BotFather) in your Telegram account:
![Botfather](./media/botfather.png)
- Create a new Bot using /newbot command: It will ask you 2 questions, A name and a username for your bot. Note that the username has to end with the word "bot".
![Create new bot](./media/newbot.png)
- Start the bot you've just created. You can find the link to your bot here.
![token](./media/token.png)
- Enter your panel and config Telegram bot settings like below:
![Panel Config](./media/panel-bot-config.png)
Enter your bot token in input field number 3.
Enter the user ID in input field number 4. The Telegram accounts with this id will be the bot admin. (You can enter more than one, Just separate them with ,)
- How to get Telegram user ID? Use this [bot](https://t.me/useridinfobot), Start the bot and it will give you the Telegram user ID.
![User ID](./media/user-id.png)
</details>
## API Routes
<details>
<summary>Click for API routes details</summary>
#### Usage
- `/login` with `POST` user data: `{username: '', password: ''}` for login
- `/panel/api/inbounds` base for following actions:
| Method | Path | Action |
| :----: | ---------------------------------- | ------------------------------------------- |
| `GET` | `"/list"` | Get all inbounds |
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
| `POST` | `"/add"` | Add inbound |
| `POST` | `"/del/:id"` | Delete Inbound |
| `POST` | `"/update/:id"` | Update Inbound |
| `POST` | `"/clientIps/:email"` | Client Ip address |
| `POST` | `"/clearClientIps/:email"` | Clear Client Ip address |
| `POST` | `"/addClient"` | Add Client to inbound |
| `POST` | `"/:id/delClient/:clientId"` | Delete Client by clientId\* |
| `POST` | `"/updateClient/:clientId"` | Update Client by clientId\* |
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset Client's Traffic |
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
| `POST` | `"/onlines"` | Get Online users ( list of emails ) |
\*- The field `clientId` should be filled by:
- `client.id` for VMESS and VLESS
- `client.password` for TROJAN
- `client.email` for Shadowsocks
- [API Documentation](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
</details>
## Environment Variables
<details>
<summary>Click for environment variables details</summary>
#### Usage
| Variable | Type | Default |
| -------------- | :--------------------------------------------: | :------------ |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
Example:
```sh
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
```
</details>
## Preview
![1](./media/1.png)
![2](./media/2.png)
![3](./media/3.png)
![4](./media/4.png)
![5](./media/5.png)
![6](./media/6.png)
![7](./media/7.png)
For full documentation, please visit the [project Wiki](https://github.com/MHSanaei/3x-ui/wiki).
## A Special Thanks to
@@ -486,8 +37,21 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
## Acknowledgment
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _This repository contains automatically updated V2Ray routing rules based on data on blocked domains and addresses in Russia._
## Support project
**If this project is helpful to you, you may wish to give it a**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## Stargazers over Time
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui)
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

57
README.ru_RU.md Normal file
View File

@@ -0,0 +1,57 @@
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
<img alt="3x-ui" src="./media/3x-ui-light.png">
</picture>
</p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v2.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v2)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v2)
**3X-UI** — продвинутая панель управления с открытым исходным кодом на основе веб-интерфейса, разработанная для управления сервером Xray-core. Предоставляет удобный интерфейс для настройки и мониторинга различных VPN и прокси-протоколов.
> [!IMPORTANT]
> Этот проект предназначен только для личного использования, пожалуйста, не используйте его в незаконных целях и в производственной среде.
Как улучшенная версия оригинального проекта X-UI, 3X-UI обеспечивает повышенную стабильность, более широкую поддержку протоколов и дополнительные функции.
## Быстрый старт
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
Полную документацию смотрите в [вики проекта](https://github.com/MHSanaei/3x-ui/wiki).
## Особая благодарность
- [alireza0](https://github.com/alireza0/)
## Благодарности
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Лицензия: **GPL-3.0**): _Улучшенные правила маршрутизации для v2ray/xray и v2ray/xray-clients со встроенными иранскими доменами и фокусом на безопасность и блокировку рекламы._
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Лицензия: **GPL-3.0**): _Этот репозиторий содержит автоматически обновляемые правила маршрутизации V2Ray на основе данных о заблокированных доменах и адресах в России._
## Поддержка проекта
**Если этот проект полезен для вас, вы можете поставить ему**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## Звезды с течением времени
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View File

@@ -1,474 +0,0 @@
# 3X-UI
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
<p align="center"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
**一个更好的面板 • 基于Xray Core构建**
[![](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](#)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](#)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
> **Disclaimer:** 此项目仅供个人学习交流,请不要用于非法目的,请不要在生产环境中使用。
**如果此项目对你有用,请给一个**:star2:
<p align="left"><a href="#"><img width="125" src="https://github.com/MHSanaei/3x-ui/assets/115543613/7aa895dd-048a-42e7-989b-afd41a74e2e1" alt="Image"></a></p>
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
## 安装 & 升级
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
## 安装指定版本
要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.3.6`:
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.6
```
## SSL 认证
<details>
<summary>点击查看 SSL 认证</summary>
### Cloudflare
管理脚本具有用于 Cloudflare 的内置 SSL 证书应用程序。若要使用此脚本申请证书,需要满足以下条件:
- Cloudflare 邮箱地址
- Cloudflare Global API Key
- 域名已通过 cloudflare 解析到当前服务器
**1:** 在终端中运行`x-ui` 选择 `Cloudflare SSL Certificate`.
### Certbot
```
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
***Tip:*** *管理脚本具有 Certbot 。使用 `x-ui` 命令, 选择 `SSL Certificate Management`.*
</details>
## 手动安装 & 升级
<details>
<summary>点击查看 手动安装 & 升级</summary>
#### 使用
1. 若要将最新版本的压缩包直接下载到服务器,请运行以下命令:
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
*) XUI_ARCH="amd64" ;;
esac
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
```
2. 下载压缩包后,执行以下命令安装或升级 x-ui
```sh
ARCH=$(uname -m)
case "${ARCH}" in
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
i*86 | x86) XUI_ARCH="386" ;;
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
armv7* | armv7) XUI_ARCH="armv7" ;;
armv6* | armv6) XUI_ARCH="armv6" ;;
armv5* | armv5) XUI_ARCH="armv5" ;;
*) XUI_ARCH="amd64" ;;
esac
cd /root/
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
cp x-ui/x-ui.sh /usr/bin/x-ui
cp -f x-ui/x-ui.service /etc/systemd/system/
mv x-ui/ /usr/local/
systemctl daemon-reload
systemctl enable x-ui
systemctl restart x-ui
```
</details>
## 通过Docker安装
<details>
<summary>点击查看 通过Docker安装</summary>
#### 使用
1. 安装Docker
```sh
bash <(curl -sSL https://get.docker.com)
```
2. 克隆仓库:
```sh
git clone https://github.com/MHSanaei/3x-ui.git
cd 3x-ui
```
3. 运行服务:
```sh
docker compose up -d
```
```sh
docker run -itd \
-e XRAY_VMESS_AEAD_FORCED=false \
-v $PWD/db/:/etc/x-ui/ \
-v $PWD/cert/:/root/cert/ \
--network=host \
--restart=unless-stopped \
--name 3x-ui \
ghcr.io/mhsanaei/3x-ui:latest
```
更新至最新版本
```sh
cd 3x-ui
docker compose down
docker compose pull 3x-ui
docker compose up -d
```
从Docker中删除3x-ui
```sh
docker stop 3x-ui
docker rm 3x-ui
cd --
rm -r 3x-ui
```
</details>
## 建议使用的操作系统
- Ubuntu 20.04+
- Debian 11+
- CentOS 8+
- Fedora 36+
- Arch Linux
- Manjaro
- Armbian
- AlmaLinux 9+
- Rockylinux 9+
- OpenSUSE Tubleweed
## 支持的架构和设备
<details>
<summary>点击查看 支持的架构和设备</summary>
我们的平台提供与各种架构和设备的兼容性,确保在各种计算环境中的灵活性。以下是我们支持的关键架构:
- **amd64**: 这种流行的架构是个人计算机和服务器的标准,可以无缝地适应大多数现代操作系统。
- **x86 / i386**: 这种架构在台式机和笔记本电脑中被广泛采用,得到了众多操作系统和应用程序的广泛支持,包括但不限于 Windows、macOS 和 Linux 系统。
- **armv8 / arm64 / aarch64**: 这种架构专为智能手机和平板电脑等当代移动和嵌入式设备量身定制,以 Raspberry Pi 4、Raspberry Pi 3、Raspberry Pi Zero 2/Zero 2 W、Orange Pi 3 LTS 等设备为例。
- **armv7 / arm / arm32**: 作为较旧的移动和嵌入式设备的架构它仍然广泛用于Orange Pi Zero LTS、Orange Pi PC Plus、Raspberry Pi 2等设备。
- **armv6 / arm / arm32**: 这种架构面向非常老旧的嵌入式设备虽然不太普遍但仍在使用中。Raspberry Pi 1、Raspberry Pi Zero/Zero W 等设备都依赖于这种架构。
- **armv5 / arm / arm32**: 它是一种主要与早期嵌入式系统相关的旧架构,目前不太常见,但仍可能出现在早期 Raspberry Pi 版本和一些旧智能手机等传统设备中。
</details>
## Languages
- English英语
- Farsi伊朗语
- Chinese中文
- Russian俄语
- Vietnamese越南语
- Spanish西班牙语
- Indonesian (印度尼西亚语)
- Ukrainian乌克兰语
## Features
- 系统状态监控
- 在所有入站和客户端中搜索
- 深色/浅色主题
- 支持多用户和多协议
- 支持多种协议,包括 VMess、VLESS、Trojan、Shadowsocks、Dokodemo-door、Socks、HTTP、wireguard
- 支持 XTLS 原生协议,包括 RPRX-Direct、Vision、REALITY
- 流量统计、流量限制、过期时间限制
- 可自定义的 Xray配置模板
- 支持HTTPS访问面板自建域名+SSL证书
- 支持一键式SSL证书申请和自动续费
- 更多高级配置项目请参考面板
- 修复了 API 路由(用户设置将使用 API 创建)
- 支持通过面板中提供的不同项目更改配置。
- 支持从面板导出/导入数据库
## 默认设置
<details>
<summary>点击查看 默认设置</summary>
### 信息
- **端口:** 2053
- **用户名 & 密码:** 当您跳过设置时,此项会随机生成。
- **数据库路径:**
- /etc/x-ui/x-ui.db
- **Xray 配置路径:**
- /usr/local/x-ui/bin/config.json
- **面板链接无SSL**
- http://ip:2053/panel
- http://domain:2053/panel
- **面板链接有SSL**
- https://domain:2053/panel
</details>
## WARP 配置
<details>
<summary>点击查看 WARP 配置</summary>
#### 使用
如果要在 v2.1.0 之前使用 WARP 路由,请按照以下步骤操作:
**1.** 在 **SOCKS Proxy Mode** 模式中安装Wrap
```sh
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
```
**2.** 如果您已经安装了 warp您可以使用以下命令卸载
```sh
warp u
```
**3.** 在面板中打开您需要的配置
配置:
- Block Ads
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
- Fix Google 403 error
</details>
## IP 限制
<details>
<summary>点击查看 IP 限制</summary>
#### 使用
**注意:** 使用 IP 隧道时IP 限制无法正常工作。
- 适用于最高 `v1.6.1`
- IP 限制 已被集成在面板中。
- 适用于 `v1.7.0` 以及更新的版本:
- 要使 IP 限制正常工作,您需要按照以下步骤安装 fail2ban 及其所需的文件:
1. 使用面板内置的 `x-ui` 指令
2. 选择 `IP Limit Management`.
3. 根据您的需要选择合适的选项。
- 确保您的 Xray 配置上有 ./access.log 。在 v2.1.3 之后,我们有一个选项。
```sh
"log": {
"access": "./access.log",
"dnsLog": false,
"loglevel": "warning"
},
```
</details>
## Telegram 机器人
<details>
<summary>点击查看 Telegram 机器人</summary>
#### 使用
Web 面板通过 Telegram Bot 支持每日流量、面板登录、数据库备份、系统状态、客户端信息等通知和功能。要使用机器人,您需要在面板中设置机器人相关参数,包括:
- 电报令牌
- 管理员聊天 ID
- 通知时间cron 语法)
- 到期日期通知
- 流量上限通知
- 数据库备份
- CPU 负载通知
**参考:**
- `30 \* \* \* \* \*` - 在每个点的 30 秒处通知
- `0 \*/10 \* \* \* \*` - 每 10 分钟的第一秒通知
- `@hourly` - 每小时通知
- `@daily` - 每天通知 (00:00)
- `@weekly` - 每周通知
- `@every 8h` - 每8小时通知
### Telegram Bot 功能
- 定期报告
- 登录通知
- CPU 阈值通知
- 提前报告的过期时间和流量阈值
- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单
- 支持使用UUIDVMESS/VLESS或密码TROJAN搜索报文流量报告 - 匿名
- 基于菜单的机器人
- 通过电子邮件搜索客户端(仅限管理员)
- 检查所有入库
- 检查服务器状态
- 检查耗尽的用户
- 根据请求和定期报告接收备份
- 多语言机器人
### 注册 Telegram bot
- 与 [Botfather](https://t.me/BotFather) 对话:
![Botfather](./media/botfather.png)
- 使用 /newbot 创建新机器人你需要提供机器人名称以及用户名注意名称中末尾要包含“bot”
![创建机器人](./media/newbot.png)
- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。
![令牌](./media/token.png)
- 输入您的面板并配置 Telegram 机器人设置,如下所示:
![面板设置](./media/panel-bot-config.png)
在输入字段编号 3 中输入机器人令牌。
在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可)
- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot) 启动机器人,它会给你 Telegram 用户 ID。
![用户 ID](./media/user-id.png)
</details>
## API 路由
<details>
<summary>点击查看 API 路由</summary>
#### 使用
- `/login` 使用 `POST` 用户名称 & 密码: `{username: '', password: ''}` 登录
- `/panel/api/inbounds` 以下操作的基础:
| 方法 | 路径 | 操作 |
| :----: | ---------------------------------- | --------------------------------- |
| `GET` | `"/list"` | 获取所有入站 |
| `GET` | `"/get/:id"` | 获取所有入站以及inbound.id |
| `GET` | `"/getClientTraffics/:email"` | 通过电子邮件获取客户端流量 |
| `GET` | `"/createbackup"` | Telegram 机器人向管理员发送备份 |
| `POST` | `"/add"` | 添加入站 |
| `POST` | `"/del/:id"` | 删除入站 |
| `POST` | `"/update/:id"` | 更新入站 |
| `POST` | `"/clientIps/:email"` | 客户端 IP 地址 |
| `POST` | `"/clearClientIps/:email"` | 清除客户端 IP 地址 |
| `POST` | `"/addClient"` | 将客户端添加到入站 |
| `POST` | `"/:id/delClient/:clientId"` | 通过 clientId\* 删除客户端 |
| `POST` | `"/updateClient/:clientId"` | 通过 clientId\* 更新客户端 |
| `POST` | `"/:id/resetClientTraffic/:email"` | 重置客户端的流量 |
| `POST` | `"/resetAllTraffics"` | 重置所有入站的流量 |
| `POST` | `"/resetAllClientTraffics/:id"` | 重置入站中所有客户端的流量 |
| `POST` | `"/delDepletedClients/:id"` | 删除入站耗尽的客户端 -1 all |
| `POST` | `"/onlines"` | 获取在线用户 电子邮件列表 |
\*- `clientId` 项应该使用下列数据
- `client.id` VMESS and VLESS
- `client.password` TROJAN
- `client.email` Shadowsocks
- [API 文档](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm)
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
</details>
## 环境变量
<details>
<summary>点击查看 环境变量</summary>
#### Usage
| 变量 | Type | 默认 |
| -------------- | :--------------------------------------------: | :------------ |
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
| XUI_DEBUG | `boolean` | `false` |
| XUI_BIN_FOLDER | `string` | `"bin"` |
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
例子:
```sh
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
```
</details>
## 预览
![1](./media/1.png)
![2](./media/2.png)
![3](./media/3.png)
![4](./media/4.png)
![5](./media/5.png)
![6](./media/6.png)
![7](./media/7.png)
## 特别感谢
- [alireza0](https://github.com/alireza0/)
## 致谢
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
- [Vietnam Adblock rules](https://github.com/vuong2023/vn-v2ray-rules) (License: **GPL-3.0**): _A hosted domain hosted in Vietnam and blocklist with the most efficiency for Vietnamese._
## Star趋势
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg)](https://starchart.cc/MHSanaei/3x-ui)

57
README.zh_CN.md Normal file
View File

@@ -0,0 +1,57 @@
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
<img alt="3x-ui" src="./media/3x-ui-light.png">
</picture>
</p>
[![Release](https://img.shields.io/github/v/release/mhsanaei/3x-ui.svg)](https://github.com/MHSanaei/3x-ui/releases)
[![Build](https://img.shields.io/github/actions/workflow/status/mhsanaei/3x-ui/release.yml.svg)](https://github.com/MHSanaei/3x-ui/actions)
[![GO Version](https://img.shields.io/github/go-mod/go-version/mhsanaei/3x-ui.svg)](#)
[![Downloads](https://img.shields.io/github/downloads/mhsanaei/3x-ui/total.svg)](https://github.com/MHSanaei/3x-ui/releases/latest)
[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
[![Go Reference](https://pkg.go.dev/badge/github.com/mhsanaei/3x-ui/v2.svg)](https://pkg.go.dev/github.com/mhsanaei/3x-ui/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/mhsanaei/3x-ui/v2)](https://goreportcard.com/report/github.com/mhsanaei/3x-ui/v2)
**3X-UI** — 一个基于网页的高级开源控制面板,专为管理 Xray-core 服务器而设计。它提供了用户友好的界面,用于配置和监控各种 VPN 和代理协议。
> [!IMPORTANT]
> 本项目仅用于个人使用和通信,请勿将其用于非法目的,请勿在生产环境中使用。
作为原始 X-UI 项目的增强版本3X-UI 提供了更好的稳定性、更广泛的协议支持和额外的功能。
## 快速开始
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
```
完整文档请参阅 [项目Wiki](https://github.com/MHSanaei/3x-ui/wiki)。
## 特别感谢
- [alireza0](https://github.com/alireza0/)
## 致谢
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (许可证: **GPL-3.0**): _增强的 v2ray/xray 和 v2ray/xray-clients 路由规则,内置伊朗域名,专注于安全性和广告拦截。_
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (许可证: **GPL-3.0**): _此仓库包含基于俄罗斯被阻止域名和地址数据自动更新的 V2Ray 路由规则。_
## 支持项目
**如果这个项目对您有帮助,您可以给它一个**:star2:
<a href="https://www.buymeacoffee.com/MHSanaei" target="_blank">
<img src="./media/default-yellow.png" alt="Buy Me A Coffee" style="height: 70px !important;width: 277px !important;" >
</a>
</br>
<a href="https://nowpayments.io/donation/hsanaei" target="_blank" rel="noreferrer noopener">
<img src="./media/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
</a>
## 随时间变化的星标数
[![Stargazers over time](https://starchart.cc/MHSanaei/3x-ui.svg?variant=adaptive)](https://starchart.cc/MHSanaei/3x-ui)

View File

@@ -1,9 +1,14 @@
// Package config provides configuration management utilities for the 3x-ui panel,
// including version information, logging levels, database paths, and environment variable handling.
package config
import (
_ "embed"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
)
@@ -13,24 +18,29 @@ var version string
//go:embed name
var name string
// LogLevel represents the logging level for the application.
type LogLevel string
// Logging level constants
const (
Debug LogLevel = "debug"
Info LogLevel = "info"
Notice LogLevel = "notice"
Warn LogLevel = "warn"
Error LogLevel = "error"
Debug LogLevel = "debug"
Info LogLevel = "info"
Notice LogLevel = "notice"
Warning LogLevel = "warning"
Error LogLevel = "error"
)
// GetVersion returns the version string of the 3x-ui application.
func GetVersion() string {
return strings.TrimSpace(version)
}
// GetName returns the name of the 3x-ui application.
func GetName() string {
return strings.TrimSpace(name)
}
// GetLogLevel returns the current logging level based on environment variables or defaults to Info.
func GetLogLevel() LogLevel {
if IsDebug() {
return Debug
@@ -42,10 +52,12 @@ func GetLogLevel() LogLevel {
return LogLevel(logLevel)
}
// IsDebug returns true if debug mode is enabled via the XUI_DEBUG environment variable.
func IsDebug() bool {
return os.Getenv("XUI_DEBUG") == "true"
}
// GetBinFolderPath returns the path to the binary folder, defaulting to "bin" if not set via XUI_BIN_FOLDER.
func GetBinFolderPath() string {
binFolderPath := os.Getenv("XUI_BIN_FOLDER")
if binFolderPath == "" {
@@ -54,22 +66,91 @@ func GetBinFolderPath() string {
return binFolderPath
}
func GetDBFolderPath() string {
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath == "" {
dbFolderPath = "/etc/x-ui"
func getBaseDir() string {
exePath, err := os.Executable()
if err != nil {
return "."
}
return dbFolderPath
exeDir := filepath.Dir(exePath)
exeDirLower := strings.ToLower(filepath.ToSlash(exeDir))
if strings.Contains(exeDirLower, "/appdata/local/temp/") || strings.Contains(exeDirLower, "/go-build") {
wd, err := os.Getwd()
if err != nil {
return "."
}
return wd
}
return exeDir
}
// GetDBFolderPath returns the path to the database folder based on environment variables or platform defaults.
func GetDBFolderPath() string {
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
if dbFolderPath != "" {
return dbFolderPath
}
if runtime.GOOS == "windows" {
return getBaseDir()
}
return "/etc/x-ui"
}
// GetDBPath returns the full path to the database file.
func GetDBPath() string {
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
}
// GetLogFolder returns the path to the log folder based on environment variables or platform defaults.
func GetLogFolder() string {
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
if logFolderPath == "" {
logFolderPath = "/var/log"
if logFolderPath != "" {
return logFolderPath
}
return logFolderPath
if runtime.GOOS == "windows" {
return filepath.Join(".", "log")
}
return "/var/log"
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return out.Sync()
}
func init() {
if runtime.GOOS != "windows" {
return
}
if os.Getenv("XUI_DB_FOLDER") != "" {
return
}
oldDBFolder := "/etc/x-ui"
oldDBPath := fmt.Sprintf("%s/%s.db", oldDBFolder, GetName())
newDBFolder := GetDBFolderPath()
newDBPath := fmt.Sprintf("%s/%s.db", newDBFolder, GetName())
_, err := os.Stat(newDBPath)
if err == nil {
return // new exists
}
_, err = os.Stat(oldDBPath)
if os.IsNotExist(err) {
return // old does not exist
}
_ = copyFile(oldDBPath, newDBPath) // ignore error
}

View File

@@ -1 +1 @@
2.3.6
2.8.5

View File

@@ -1,15 +1,21 @@
// Package database provides database initialization, migration, and management utilities
// for the 3x-ui panel using GORM with SQLite.
package database
import (
"bytes"
"errors"
"io"
"io/fs"
"log"
"os"
"path"
"slices"
"x-ui/config"
"x-ui/database/model"
"x-ui/xray"
"github.com/mhsanaei/3x-ui/v2/config"
"github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/util/crypto"
"github.com/mhsanaei/3x-ui/v2/xray"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
@@ -18,56 +24,102 @@ import (
var db *gorm.DB
var initializers = []func() error{
initUser,
initInbound,
initOutbound,
initSetting,
initInboundClientIps,
initClientTraffic,
const (
defaultUsername = "admin"
defaultPassword = "admin"
)
func initModels() error {
models := []any{
&model.User{},
&model.Inbound{},
&model.OutboundTraffics{},
&model.Setting{},
&model.InboundClientIps{},
&xray.ClientTraffic{},
&model.HistoryOfSeeders{},
}
for _, model := range models {
if err := db.AutoMigrate(model); err != nil {
log.Printf("Error auto migrating model: %v", err)
return err
}
}
return nil
}
// initUser creates a default admin user if the users table is empty.
func initUser() error {
err := db.AutoMigrate(&model.User{})
empty, err := isTableEmpty("users")
if err != nil {
log.Printf("Error checking if users table is empty: %v", err)
return err
}
var count int64
err = db.Model(&model.User{}).Count(&count).Error
if err != nil {
return err
}
if count == 0 {
if empty {
hashedPassword, err := crypto.HashPasswordAsBcrypt(defaultPassword)
if err != nil {
log.Printf("Error hashing default password: %v", err)
return err
}
user := &model.User{
Username: "admin",
Password: "admin",
LoginSecret: "",
Username: defaultUsername,
Password: hashedPassword,
}
return db.Create(user).Error
}
return nil
}
func initInbound() error {
return db.AutoMigrate(&model.Inbound{})
// runSeeders migrates user passwords to bcrypt and records seeder execution to prevent re-running.
func runSeeders(isUsersEmpty bool) error {
empty, err := isTableEmpty("history_of_seeders")
if err != nil {
log.Printf("Error checking if users table is empty: %v", err)
return err
}
if empty && isUsersEmpty {
hashSeeder := &model.HistoryOfSeeders{
SeederName: "UserPasswordHash",
}
return db.Create(hashSeeder).Error
} else {
var seedersHistory []string
db.Model(&model.HistoryOfSeeders{}).Pluck("seeder_name", &seedersHistory)
if !slices.Contains(seedersHistory, "UserPasswordHash") && !isUsersEmpty {
var users []model.User
db.Find(&users)
for _, user := range users {
hashedPassword, err := crypto.HashPasswordAsBcrypt(user.Password)
if err != nil {
log.Printf("Error hashing password for user '%s': %v", user.Username, err)
return err
}
db.Model(&user).Update("password", hashedPassword)
}
hashSeeder := &model.HistoryOfSeeders{
SeederName: "UserPasswordHash",
}
return db.Create(hashSeeder).Error
}
}
return nil
}
func initOutbound() error {
return db.AutoMigrate(&model.OutboundTraffics{})
}
func initSetting() error {
return db.AutoMigrate(&model.Setting{})
}
func initInboundClientIps() error {
return db.AutoMigrate(&model.InboundClientIps{})
}
func initClientTraffic() error {
return db.AutoMigrate(&xray.ClientTraffic{})
// isTableEmpty returns true if the named table contains zero rows.
func isTableEmpty(tableName string) (bool, error) {
var count int64
err := db.Table(tableName).Count(&count).Error
return count == 0, err
}
// InitDB sets up the database connection, migrates models, and runs seeders.
func InitDB(dbPath string) error {
dir := path.Dir(dbPath)
err := os.MkdirAll(dir, fs.ModePerm)
@@ -91,23 +143,44 @@ func InitDB(dbPath string) error {
return err
}
for _, initialize := range initializers {
if err := initialize(); err != nil {
return err
}
if err := initModels(); err != nil {
return err
}
isUsersEmpty, err := isTableEmpty("users")
if err != nil {
return err
}
if err := initUser(); err != nil {
return err
}
return runSeeders(isUsersEmpty)
}
// CloseDB closes the database connection if it exists.
func CloseDB() error {
if db != nil {
sqlDB, err := db.DB()
if err != nil {
return err
}
return sqlDB.Close()
}
return nil
}
// GetDB returns the global GORM database instance.
func GetDB() *gorm.DB {
return db
}
// IsNotFound checks if the given error is a GORM record not found error.
func IsNotFound(err error) bool {
return err == gorm.ErrRecordNotFound
}
// IsSQLiteDB checks if the given file is a valid SQLite database by reading its signature.
func IsSQLiteDB(file io.ReaderAt) (bool, error) {
signature := []byte("SQLite format 3\x00")
buf := make([]byte, len(signature))
@@ -118,6 +191,7 @@ func IsSQLiteDB(file io.ReaderAt) (bool, error) {
return bytes.Equal(buf, signature), nil
}
// Checkpoint performs a WAL checkpoint on the SQLite database to ensure data consistency.
func Checkpoint() error {
// Update WAL
err := db.Exec("PRAGMA wal_checkpoint;").Error
@@ -126,3 +200,29 @@ func Checkpoint() error {
}
return nil
}
// ValidateSQLiteDB opens the provided sqlite DB path with a throw-away connection
// and runs a PRAGMA integrity_check to ensure the file is structurally sound.
// It does not mutate global state or run migrations.
func ValidateSQLiteDB(dbPath string) error {
if _, err := os.Stat(dbPath); err != nil { // file must exist
return err
}
gdb, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{Logger: logger.Discard})
if err != nil {
return err
}
sqlDB, err := gdb.DB()
if err != nil {
return err
}
defer sqlDB.Close()
var res string
if err := gdb.Raw("PRAGMA integrity_check;").Scan(&res).Error; err != nil {
return err
}
if res != "ok" {
return errors.New("sqlite integrity check failed: " + res)
}
return nil
}

View File

@@ -1,44 +1,51 @@
// Package model defines the database models and data structures used by the 3x-ui panel.
package model
import (
"fmt"
"x-ui/util/json_util"
"x-ui/xray"
"github.com/mhsanaei/3x-ui/v2/util/json_util"
"github.com/mhsanaei/3x-ui/v2/xray"
)
// Protocol represents the protocol type for Xray inbounds.
type Protocol string
// Protocol constants for different Xray inbound protocols
const (
VMess Protocol = "vmess"
VMESS Protocol = "vmess"
VLESS Protocol = "vless"
DOKODEMO Protocol = "dokodemo-door"
Tunnel Protocol = "tunnel"
HTTP Protocol = "http"
Trojan Protocol = "trojan"
Shadowsocks Protocol = "shadowsocks"
Socks Protocol = "socks"
Mixed Protocol = "mixed"
WireGuard Protocol = "wireguard"
)
// User represents a user account in the 3x-ui panel.
type User struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
Username string `json:"username"`
Password string `json:"password"`
LoginSecret string `json:"loginSecret"`
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
Username string `json:"username"`
Password string `json:"password"`
}
// Inbound represents an Xray inbound configuration with traffic statistics and settings.
type Inbound struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
UserId int `json:"-"`
Up int64 `json:"up" form:"up"`
Down int64 `json:"down" form:"down"`
Total int64 `json:"total" form:"total"`
Remark string `json:"remark" form:"remark"`
Enable bool `json:"enable" form:"enable"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` // Unique identifier
UserId int `json:"-"` // Associated user ID
Up int64 `json:"up" form:"up"` // Upload traffic in bytes
Down int64 `json:"down" form:"down"` // Download traffic in bytes
Total int64 `json:"total" form:"total"` // Total traffic limit in bytes
AllTime int64 `json:"allTime" form:"allTime" gorm:"default:0"` // All-time traffic usage
Remark string `json:"remark" form:"remark"` // Human-readable remark
Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"` // Whether the inbound is enabled
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp
TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2"` // Traffic reset schedule
LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"` // Last traffic reset timestamp
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` // Client traffic statistics
// config part
// Xray configuration fields
Listen string `json:"listen" form:"listen"`
Port int `json:"port" form:"port"`
Protocol Protocol `json:"protocol" form:"protocol"`
@@ -48,6 +55,7 @@ type Inbound struct {
Sniffing string `json:"sniffing" form:"sniffing"`
}
// OutboundTraffics tracks traffic statistics for Xray outbound connections.
type OutboundTraffics struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Tag string `json:"tag" form:"tag" gorm:"unique"`
@@ -56,12 +64,20 @@ type OutboundTraffics struct {
Total int64 `json:"total" form:"total" gorm:"default:0"`
}
// InboundClientIps stores IP addresses associated with inbound clients for access control.
type InboundClientIps struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
Ips string `json:"ips" form:"ips"`
}
// HistoryOfSeeders tracks which database seeders have been executed to prevent re-running.
type HistoryOfSeeders struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
SeederName string `json:"seederName"`
}
// GenXrayInboundConfig generates an Xray inbound configuration from the Inbound model.
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
listen := i.Listen
if listen != "" {
@@ -78,22 +94,28 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
}
}
// Setting stores key-value configuration settings for the 3x-ui panel.
type Setting struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Key string `json:"key" form:"key"`
Value string `json:"value" form:"value"`
}
// Client represents a client configuration for Xray inbounds with traffic limits and settings.
type Client struct {
ID string `json:"id"`
Password string `json:"password"`
Flow string `json:"flow"`
Email string `json:"email"`
LimitIP int `json:"limitIp"`
TotalGB int64 `json:"totalGB" form:"totalGB"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
Enable bool `json:"enable" form:"enable"`
TgID int64 `json:"tgId" form:"tgId"`
SubID string `json:"subId" form:"subId"`
Reset int `json:"reset" form:"reset"`
ID string `json:"id"` // Unique client identifier
Security string `json:"security"` // Security method (e.g., "auto", "aes-128-gcm")
Password string `json:"password"` // Client password
Flow string `json:"flow"` // Flow control (XTLS)
Email string `json:"email"` // Client email identifier
LimitIP int `json:"limitIp"` // IP limit for this client
TotalGB int64 `json:"totalGB" form:"totalGB"` // Total traffic limit in GB
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp
Enable bool `json:"enable" form:"enable"` // Whether the client is enabled
TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications
SubID string `json:"subId" form:"subId"` // Subscription identifier
Comment string `json:"comment" form:"comment"` // Client comment
Reset int `json:"reset" form:"reset"` // Reset period in days
CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp
UpdatedAt int64 `json:"updated_at,omitempty"` // Last update timestamp
}

View File

@@ -1,16 +1,16 @@
---
version: "3"
services:
3x-ui:
image: ghcr.io/mhsanaei/3x-ui:latest
container_name: 3x-ui
hostname: yourhostname
3xui:
build:
context: .
dockerfile: ./Dockerfile
container_name: 3xui_app
# hostname: yourhostname <- optional
volumes:
- $PWD/db/:/etc/x-ui/
- $PWD/cert/:/root/cert/
environment:
XRAY_VMESS_AEAD_FORCED: "false"
XUI_ENABLE_FAIL2BAN: "true"
tty: true
network_mode: host
restart: unless-stopped

140
go.mod
View File

@@ -1,100 +1,104 @@
module x-ui
module github.com/mhsanaei/3x-ui/v2
go 1.22.4
go 1.25.2
require (
github.com/gin-contrib/gzip v1.0.1
github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.3
github.com/mymmrac/telego v0.30.2
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/gin-contrib/gzip v1.2.4
github.com/gin-contrib/sessions v1.0.4
github.com/gin-gonic/gin v1.11.0
github.com/go-ldap/ldap/v3 v3.4.12
github.com/goccy/go-json v0.10.5
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/mymmrac/telego v1.3.0
github.com/nicksnyder/go-i18n/v2 v2.6.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.2
github.com/pelletier/go-toml/v2 v2.2.4
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.24.5
github.com/valyala/fasthttp v1.55.0
github.com/xtls/xray-core v1.8.15
github.com/shirou/gopsutil/v4 v4.25.9
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/valyala/fasthttp v1.67.0
github.com/xlzd/gotp v0.1.0
github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6
go.uber.org/atomic v1.11.0
golang.org/x/text v0.16.0
google.golang.org/grpc v1.64.0
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.10
golang.org/x/crypto v0.43.0
golang.org/x/sys v0.37.0
golang.org/x/text v0.30.0
google.golang.org/grpc v1.76.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.0
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.11.8 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/fasthttp/router v1.5.1 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.14.1 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
github.com/ebitengine/purego v0.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.21.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
github.com/go-playground/validator/v10 v10.28.0 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grbit/go-json v0.11.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/miekg/dns v1.1.68 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/quic-go/quic-go v0.45.0 // indirect
github.com/refraction-networking/utls v1.6.6 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.55.0 // indirect
github.com/refraction-networking/utls v1.8.1 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sagernet/sing v0.4.1 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.7.12 // indirect
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc // indirect
github.com/vishvananda/netlink v1.3.1 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.38.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
google.golang.org/protobuf v1.36.10 // indirect
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
lukechampine.com/blake3 v1.4.1 // indirect
)

508
go.sum
View File

@@ -1,65 +1,50 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bytedance/sonic v1.11.8 h1:Zw/j1KfiS+OYTi9lyB3bb0CFxPJVkM17k1wyDG32LRA=
github.com/bytedance/sonic v1.11.8/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fasthttp/router v1.5.1 h1:uViy8UYYhm5npJSKEZ4b/ozM//NGzVCfJbh6VJ0VKr8=
github.com/fasthttp/router v1.5.1/go.mod h1:WrmsLo3mrerZP2VEXRV1E8nL8ymJFYCDTr4HmnB8+Zs=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/gin-contrib/gzip v1.2.4 h1:yNz4EhPC2kHSZJD1oc1zwp7MLEhEZ3goQeGM3a1b6jU=
github.com/gin-contrib/gzip v1.2.4/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -69,343 +54,228 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.21.0 h1:4fZA11ovvtkdgaeev9RGWPgc1uj3H8W+rNYyH/ySBb0=
github.com/go-playground/validator/v10 v10.21.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.30.2 h1:CqGlqX0hkgz9qMwdA3q+aZtSonqMOKQQrFLn/oUOTaw=
github.com/mymmrac/telego v0.30.2/go.mod h1:U6cWJBgRCzGt+s0q77x/Dh2+i+u56VTAAYKlMenhuFc=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/mymmrac/telego v1.3.0 h1:y2bDDCioLgkcs+5luUaPgTNHKel1Qh30iUxFcMUrowg=
github.com/mymmrac/telego v1.3.0/go.mod h1:0D2l/IA/gUFn4oqsi1O4/tSnlezw5jNV/ReFRDUEKk8=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v4 v4.24.5 h1:gGsArG5K6vmsh5hcFOHaPm87UD003CaDMkAOweSQjhM=
github.com/shirou/gopsutil/v4 v4.24.5/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagernet/sing v0.7.12 h1:MpMbO56crPRZTbltoj1wGk4Xj9+GiwH1wTO4s3fz1EA=
github.com/sagernet/sing v0.7.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU=
github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
github.com/valyala/fasthttp v1.67.0 h1:tqKlJMUP6iuNG8hGjK/s9J4kadH7HLV4ijEcPGsezac=
github.com/valyala/fasthttp v1.67.0/go.mod h1:qYSIpqt/0XNmShgo/8Aq8E3UYWVVwNS2QYmzd8WIEPM=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mod h1:cAAsePK2e15YDAMJNyOpGYEWNe4sIghTY7gpz4cX/Ik=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc h1:0Nj8T1n7F7+v4vRVroaJIvY6R0vNABLfPH+lzPHRJvI=
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.15 h1:8lmDV0TaqSz0Agdh4dqQstg2QJa183j2D59PKOTVc+Y=
github.com/xtls/xray-core v1.8.15/go.mod h1:tjzDQQJpFORuhf7fBsiswiexLVEeJpAfMsD0NE5xV7M=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU=
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6 h1:gwgJxWb9OABUJAYxiS33nQzk3MRVjidzBnHBrzKnxOw=
github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6/go.mod h1:72ZU/srfutsNPmw9y8SCGRy0iccvshIRk8BNGR8D2Ik=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=

View File

@@ -2,6 +2,7 @@
red='\033[0;31m'
green='\033[0;32m'
blue='\033[0;34m'
yellow='\033[0;33m'
plain='\033[0m'
@@ -36,201 +37,221 @@ arch() {
esac
}
echo "arch: $(arch)"
os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
if [[ "${release}" == "arch" ]]; then
echo "Your OS is Arch Linux"
elif [[ "${release}" == "parch" ]]; then
echo "Your OS is Parch linux"
elif [[ "${release}" == "manjaro" ]]; then
echo "Your OS is Manjaro"
elif [[ "${release}" == "armbian" ]]; then
echo "Your OS is Armbian"
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
echo "Your OS is OpenSUSE Tumbleweed"
elif [[ "${release}" == "centos" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then
echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1
fi
elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -lt 36 ]]; then
echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1
fi
elif [[ "${release}" == "debian" ]]; then
if [[ ${os_version} -lt 11 ]]; then
echo -e "${red} Please use Debian 11 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "almalinux" ]]; then
if [[ ${os_version} -lt 9 ]]; then
echo -e "${red} Please use AlmaLinux 9 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "rocky" ]]; then
if [[ ${os_version} -lt 9 ]]; then
echo -e "${red} Please use Rocky Linux 9 or higher ${plain}\n" && exit 1
fi
elif [[ "${release}" == "oracle" ]]; then
if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
fi
else
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
echo "Please ensure you are using one of the following supported operating systems:"
echo "- Ubuntu 20.04+"
echo "- Debian 11+"
echo "- CentOS 8+"
echo "- Fedora 36+"
echo "- Arch Linux"
echo "- Parch Linux"
echo "- Manjaro"
echo "- Armbian"
echo "- AlmaLinux 9+"
echo "- Rocky Linux 9+"
echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed"
exit 1
fi
echo "Arch: $(arch)"
install_base() {
case "${release}" in
ubuntu | debian | armbian)
apt-get update && apt-get install -y -q wget curl tar tzdata
;;
centos | almalinux | rocky | oracle)
centos | rhel | almalinux | rocky | ol)
yum -y update && yum install -y -q wget curl tar tzdata
;;
fedora)
fedora | amzn | virtuozzo)
dnf -y update && dnf install -y -q wget curl tar tzdata
;;
arch | manjaro | parch)
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
;;
opensuse-tumbleweed)
opensuse-tumbleweed | opensuse-leap)
zypper refresh && zypper -q install -y wget curl tar timezone
;;
alpine)
apk update && apk add wget curl tar tzdata
;;
*)
apt-get update && apt install -y -q wget curl tar tzdata
apt-get update && apt-get install -y -q wget curl tar tzdata
;;
esac
}
# This function will be called when user installed x-ui out of security
gen_random_string() {
local length="$1"
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | fold -w "$length" | head -n 1)
echo "$random_string"
}
config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
read -p "Do you want to continue with the modification [y/n]?": config_confirm
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -p "Please set up your username: " config_account
echo -e "${yellow}Your username will be: ${config_account}${plain}"
read -p "Please set up your password: " config_password
echo -e "${yellow}Your password will be: ${config_password}${plain}"
read -p "Please set up the panel port: " config_port
echo -e "${yellow}Your panel port is: ${config_port}${plain}"
read -p "Please set up the web base path (ip:port/webbasepath/): " config_webBasePath
echo -e "${yellow}Your web base path is: ${config_webBasePath}${plain}"
echo -e "${yellow}Initializing, please wait...${plain}"
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
echo -e "${yellow}Account name and password set successfully!${plain}"
/usr/local/x-ui/x-ui setting -port ${config_port}
echo -e "${yellow}Panel port set successfully!${plain}"
/usr/local/x-ui/x-ui setting -webBasePath ${config_webBasePath}
echo -e "${yellow}Web base path set successfully!${plain}"
else
echo -e "${red}Cancel...${plain}"
if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then
local usernameTemp=$(head -c 6 /dev/urandom | base64)
local passwordTemp=$(head -c 6 /dev/urandom | base64)
local webBasePathTemp=$(head -c 6 /dev/urandom | base64)
/usr/local/x-ui/x-ui setting -username ${usernameTemp} -password ${passwordTemp} -webBasePath ${webBasePathTemp}
echo -e "This is a fresh installation, will generate random login info for security concerns:"
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local URL_lists=(
"https://api4.ipify.org"
"https://ipv4.icanhazip.com"
"https://v4.api.ipinfo.io/ip"
"https://ipv4.myexternalip.com/raw"
"https://4.ident.me"
"https://check-host.net/ip"
)
local server_ip=""
for ip_address in "${URL_lists[@]}"; do
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
if [[ -n "${server_ip}" ]]; then
break
fi
done
if [[ ${#existing_webBasePath} -lt 4 ]]; then
if [[ "$existing_hasDefaultCredential" == "true" ]]; then
local config_webBasePath=$(gen_random_string 18)
local config_username=$(gen_random_string 10)
local config_password=$(gen_random_string 10)
read -rp "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -rp "Please set up the panel port: " config_port
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
else
local config_port=$(shuf -i 1024-62000 -n 1)
echo -e "${yellow}Generated random port: ${config_port}${plain}"
fi
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
echo -e "This is a fresh installation, generating random login info for security concerns:"
echo -e "###############################################"
echo -e "${green}Username: ${usernameTemp}${plain}"
echo -e "${green}Password: ${passwordTemp}${plain}"
echo -e "${green}WebBasePath: ${webBasePathTemp}${plain}"
echo -e "${green}Username: ${config_username}${plain}"
echo -e "${green}Password: ${config_password}${plain}"
echo -e "${green}Port: ${config_port}${plain}"
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
echo -e "${green}Access URL: http://${server_ip}:${config_port}/${config_webBasePath}${plain}"
echo -e "###############################################"
echo -e "${red}If you forgot your login info, you can type x-ui and then type 8 to check after installation${plain}"
else
echo -e "${red}This is your upgrade, will keep old settings. If you forgot your login info, you can type x-ui and then type 8 to check${plain}"
local config_webBasePath=$(gen_random_string 18)
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
echo -e "${green}Access URL: http://${server_ip}:${existing_port}/${config_webBasePath}${plain}"
fi
else
if [[ "$existing_hasDefaultCredential" == "true" ]]; then
local config_username=$(gen_random_string 10)
local config_password=$(gen_random_string 10)
echo -e "${yellow}Default credentials detected. Security update required...${plain}"
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}"
echo -e "Generated new random login credentials:"
echo -e "###############################################"
echo -e "${green}Username: ${config_username}${plain}"
echo -e "${green}Password: ${config_password}${plain}"
echo -e "###############################################"
else
echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}"
fi
fi
/usr/local/x-ui/x-ui migrate
}
install_x-ui() {
cd /usr/local/
# Download resources
if [ $# == 0 ]; then
last_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [[ ! -n "$last_version" ]]; then
echo -e "${red}Failed to fetch x-ui version, it maybe due to Github API restrictions, please try it later${plain}"
exit 1
tag_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [[ ! -n "$tag_version" ]]; then
echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
tag_version=$(curl -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [[ ! -n "$tag_version" ]]; then
echo -e "${red}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}"
exit 1
fi
fi
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
wget --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
if [[ $? -ne 0 ]]; then
echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}"
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
exit 1
fi
else
last_version=$1
url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz"
tag_version=$1
tag_version_numeric=${tag_version#v}
min_version="2.3.5"
if [[ "$(printf '%s\n' "$min_version" "$tag_version_numeric" | sort -V | head -n1)" != "$min_version" ]]; then
echo -e "${red}Please use a newer version (at least v2.3.5). Exiting installation.${plain}"
exit 1
fi
url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz"
echo -e "Beginning to install x-ui $1"
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
wget --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
if [[ $? -ne 0 ]]; then
echo -e "${red}Download x-ui $1 failed,please check the version exists ${plain}"
echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
exit 1
fi
fi
wget --inet4-only -O /usr/bin/x-ui-temp https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
if [[ $? -ne 0 ]]; then
echo -e "${red}Failed to download x-ui.sh${plain}"
exit 1
fi
# Stop x-ui service and remove old resources
if [[ -e /usr/local/x-ui/ ]]; then
systemctl stop x-ui
if [[ $release == "alpine" ]]; then
rc-service x-ui stop
else
systemctl stop x-ui
fi
rm /usr/local/x-ui/ -rf
fi
# Extract resources and set permissions
tar zxvf x-ui-linux-$(arch).tar.gz
rm x-ui-linux-$(arch).tar.gz -f
cd x-ui
chmod +x x-ui
chmod +x x-ui.sh
# Check the system's architecture and rename the file accordingly
if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then
mv bin/xray-linux-$(arch) bin/xray-linux-arm
chmod +x bin/xray-linux-arm
fi
chmod +x x-ui bin/xray-linux-$(arch)
cp -f x-ui.service /etc/systemd/system/
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh
# Update x-ui cli and se set permission
mv -f /usr/bin/x-ui-temp /usr/bin/x-ui
chmod +x /usr/bin/x-ui
config_after_install
systemctl daemon-reload
systemctl enable x-ui
systemctl start x-ui
echo -e "${green}x-ui ${last_version}${plain} installation finished, it is running now..."
if [[ $release == "alpine" ]]; then
wget --inet4-only -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc
if [[ $? -ne 0 ]]; then
echo -e "${red}Failed to download x-ui.rc${plain}"
exit 1
fi
chmod +x /etc/init.d/x-ui
rc-update add x-ui
rc-service x-ui start
else
cp -f x-ui.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable x-ui
systemctl start x-ui
fi
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
echo -e ""
echo -e "x-ui control menu usages: "
echo -e "----------------------------------------------"
echo -e "x-ui - Enter Admin menu"
echo -e "x-ui start - Start x-ui"
echo -e "x-ui stop - Stop x-ui"
echo -e "x-ui restart - Restart x-ui"
echo -e "x-ui status - Show x-ui status"
echo -e "x-ui enable - Enable x-ui on system startup"
echo -e "x-ui disable - Disable x-ui on system startup"
echo -e "x-ui log - Check x-ui logs"
echo -e "x-ui banlog - Check Fail2ban ban logs"
echo -e "x-ui update - Update x-ui"
echo -e "x-ui install - Install x-ui"
echo -e "x-ui uninstall - Uninstall x-ui"
echo -e "----------------------------------------------"
echo -e "┌───────────────────────────────────────────────────────┐
${blue}x-ui control menu usages (subcommands):${plain}
${blue}x-ui${plain} - Admin Management Script │
${blue}x-ui start${plain} - Start
${blue}x-ui stop${plain} - Stop │
${blue}x-ui restart${plain} - Restart │
${blue}x-ui status${plain} - Current Status │
${blue}x-ui settings${plain} - Current Settings │
${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
${blue}x-ui log${plain} - Check logs │
${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
${blue}x-ui update${plain} - Update │
${blue}x-ui legacy${plain} - legacy version │
${blue}x-ui install${plain} - Install │
${blue}x-ui uninstall${plain} - Uninstall │
└───────────────────────────────────────────────────────┘"
}
echo -e "${green}Running...${plain}"

View File

@@ -1,15 +1,29 @@
// Package logger provides logging functionality for the 3x-ui panel with
// dual-backend logging (console/syslog and file) and buffered log storage for web UI.
package logger
import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"github.com/mhsanaei/3x-ui/v2/config"
"github.com/op/go-logging"
)
const (
maxLogBufferSize = 10240 // Maximum log entries kept in memory
logFileName = "3xui.log" // Log file name
timeFormat = "2006/01/02 15:04:05" // Log timestamp format
)
var (
logger *logging.Logger
logger *logging.Logger
logFile *os.File
// logBuffer maintains recent log entries in memory for web UI retrieval
logBuffer []struct {
time string
level logging.Level
@@ -17,89 +31,164 @@ var (
}
)
func init() {
InitLogger(logging.INFO)
}
// InitLogger initializes dual logging backends: console/syslog and file.
// Console logging uses the specified level, file logging always uses DEBUG level.
func InitLogger(level logging.Level) {
newLogger := logging.MustGetLogger("x-ui")
var err error
var backend logging.Backend
var format logging.Formatter
ppid := os.Getppid()
backends := make([]logging.Backend, 0, 2)
backend, err = logging.NewSyslogBackend("")
if err != nil {
println(err)
backend = logging.NewLogBackend(os.Stderr, "", 0)
}
if ppid > 0 && err != nil {
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
} else {
format = logging.MustStringFormatter(`%{level} - %{message}`)
// Console/syslog backend with configurable level
if consoleBackend := initDefaultBackend(); consoleBackend != nil {
leveledBackend := logging.AddModuleLevel(consoleBackend)
leveledBackend.SetLevel(level, "x-ui")
backends = append(backends, leveledBackend)
}
backendFormatter := logging.NewBackendFormatter(backend, format)
backendLeveled := logging.AddModuleLevel(backendFormatter)
backendLeveled.SetLevel(level, "x-ui")
newLogger.SetBackend(backendLeveled)
// File backend with DEBUG level for comprehensive logging
if fileBackend := initFileBackend(); fileBackend != nil {
leveledBackend := logging.AddModuleLevel(fileBackend)
leveledBackend.SetLevel(logging.DEBUG, "x-ui")
backends = append(backends, leveledBackend)
}
multiBackend := logging.MultiLogger(backends...)
newLogger.SetBackend(multiBackend)
logger = newLogger
}
func Debug(args ...interface{}) {
// initDefaultBackend creates the console/syslog logging backend.
// Windows: Uses stderr directly (no syslog support)
// Unix-like: Attempts syslog, falls back to stderr
func initDefaultBackend() logging.Backend {
var backend logging.Backend
includeTime := false
if runtime.GOOS == "windows" {
// Windows: Use stderr directly (no syslog support)
backend = logging.NewLogBackend(os.Stderr, "", 0)
includeTime = true
} else {
// Unix-like: Try syslog, fallback to stderr
if syslogBackend, err := logging.NewSyslogBackend(""); err != nil {
fmt.Fprintf(os.Stderr, "syslog backend disabled: %v\n", err)
backend = logging.NewLogBackend(os.Stderr, "", 0)
includeTime = os.Getppid() > 0
} else {
backend = syslogBackend
}
}
return logging.NewBackendFormatter(backend, newFormatter(includeTime))
}
// initFileBackend creates the file logging backend.
// Creates log directory and truncates log file on startup for fresh logs.
func initFileBackend() logging.Backend {
logDir := config.GetLogFolder()
if err := os.MkdirAll(logDir, 0o750); err != nil {
fmt.Fprintf(os.Stderr, "failed to create log folder %s: %v\n", logDir, err)
return nil
}
logPath := filepath.Join(logDir, logFileName)
file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o660)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open log file %s: %v\n", logPath, err)
return nil
}
// Close previous log file if exists
if logFile != nil {
_ = logFile.Close()
}
logFile = file
backend := logging.NewLogBackend(file, "", 0)
return logging.NewBackendFormatter(backend, newFormatter(true))
}
// newFormatter creates a log formatter with optional timestamp.
func newFormatter(withTime bool) logging.Formatter {
format := `%{level} - %{message}`
if withTime {
format = `%{time:` + timeFormat + `} %{level} - %{message}`
}
return logging.MustStringFormatter(format)
}
// CloseLogger closes the log file and cleans up resources.
// Should be called during application shutdown.
func CloseLogger() {
if logFile != nil {
_ = logFile.Close()
logFile = nil
}
}
// Debug logs a debug message and adds it to the log buffer.
func Debug(args ...any) {
logger.Debug(args...)
addToBuffer("DEBUG", fmt.Sprint(args...))
}
func Debugf(format string, args ...interface{}) {
// Debugf logs a formatted debug message and adds it to the log buffer.
func Debugf(format string, args ...any) {
logger.Debugf(format, args...)
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
}
func Info(args ...interface{}) {
// Info logs an info message and adds it to the log buffer.
func Info(args ...any) {
logger.Info(args...)
addToBuffer("INFO", fmt.Sprint(args...))
}
func Infof(format string, args ...interface{}) {
// Infof logs a formatted info message and adds it to the log buffer.
func Infof(format string, args ...any) {
logger.Infof(format, args...)
addToBuffer("INFO", fmt.Sprintf(format, args...))
}
func Notice(args ...interface{}) {
// Notice logs a notice message and adds it to the log buffer.
func Notice(args ...any) {
logger.Notice(args...)
addToBuffer("NOTICE", fmt.Sprint(args...))
}
func Noticef(format string, args ...interface{}) {
// Noticef logs a formatted notice message and adds it to the log buffer.
func Noticef(format string, args ...any) {
logger.Noticef(format, args...)
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
}
func Warning(args ...interface{}) {
// Warning logs a warning message and adds it to the log buffer.
func Warning(args ...any) {
logger.Warning(args...)
addToBuffer("WARNING", fmt.Sprint(args...))
}
func Warningf(format string, args ...interface{}) {
// Warningf logs a formatted warning message and adds it to the log buffer.
func Warningf(format string, args ...any) {
logger.Warningf(format, args...)
addToBuffer("WARNING", fmt.Sprintf(format, args...))
}
func Error(args ...interface{}) {
// Error logs an error message and adds it to the log buffer.
func Error(args ...any) {
logger.Error(args...)
addToBuffer("ERROR", fmt.Sprint(args...))
}
func Errorf(format string, args ...interface{}) {
// Errorf logs a formatted error message and adds it to the log buffer.
func Errorf(format string, args ...any) {
logger.Errorf(format, args...)
addToBuffer("ERROR", fmt.Sprintf(format, args...))
}
// addToBuffer adds a log entry to the in-memory ring buffer for web UI retrieval.
func addToBuffer(level string, newLog string) {
t := time.Now()
if len(logBuffer) >= 10240 {
if len(logBuffer) >= maxLogBufferSize {
logBuffer = logBuffer[1:]
}
@@ -109,12 +198,13 @@ func addToBuffer(level string, newLog string) {
level logging.Level
log string
}{
time: t.Format("2006/01/02 15:04:05"),
time: t.Format(timeFormat),
level: logLevel,
log: newLog,
})
}
// GetLogs retrieves up to c log entries from the buffer that are at or below the specified level.
func GetLogs(c int, level string) []string {
var output []string
logLevel, _ := logging.LogLevel(level)

265
main.go
View File

@@ -1,3 +1,5 @@
// Package main is the entry point for the 3x-ui web panel application.
// It initializes the database, web server, and handles command-line operations for managing the panel.
package main
import (
@@ -9,19 +11,22 @@ import (
"syscall"
_ "unsafe"
"x-ui/config"
"x-ui/database"
"x-ui/logger"
"x-ui/sub"
"x-ui/web"
"x-ui/web/global"
"x-ui/web/service"
"github.com/mhsanaei/3x-ui/v2/config"
"github.com/mhsanaei/3x-ui/v2/database"
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/sub"
"github.com/mhsanaei/3x-ui/v2/util/crypto"
"github.com/mhsanaei/3x-ui/v2/web"
"github.com/mhsanaei/3x-ui/v2/web/global"
"github.com/mhsanaei/3x-ui/v2/web/service"
"github.com/joho/godotenv"
"github.com/op/go-logging"
)
// runWebServer initializes and starts the web server for the 3x-ui panel.
func runWebServer() {
log.Printf("%v %v", config.GetName(), config.GetVersion())
log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
switch config.GetLogLevel() {
case config.Debug:
@@ -30,36 +35,36 @@ func runWebServer() {
logger.InitLogger(logging.INFO)
case config.Notice:
logger.InitLogger(logging.NOTICE)
case config.Warn:
case config.Warning:
logger.InitLogger(logging.WARNING)
case config.Error:
logger.InitLogger(logging.ERROR)
default:
log.Fatal("unknown log level:", config.GetLogLevel())
log.Fatalf("Unknown log level: %v", config.GetLogLevel())
}
godotenv.Load()
err := database.InitDB(config.GetDBPath())
if err != nil {
log.Fatal(err)
log.Fatalf("Error initializing database: %v", err)
}
var server *web.Server
server = web.NewServer()
global.SetWebServer(server)
err = server.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error starting web server: %v", err)
return
}
var subServer *sub.Server
subServer = sub.NewServer()
global.SetSubServer(subServer)
err = subServer.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error starting sub server: %v", err)
return
}
@@ -71,55 +76,62 @@ func runWebServer() {
switch sig {
case syscall.SIGHUP:
logger.Info("Received SIGHUP signal. Restarting servers...")
err := server.Stop()
if err != nil {
logger.Warning("stop server err:", err)
logger.Debug("Error stopping web server:", err)
}
err = subServer.Stop()
if err != nil {
logger.Warning("stop server err:", err)
logger.Debug("Error stopping sub server:", err)
}
server = web.NewServer()
global.SetWebServer(server)
err = server.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error restarting web server: %v", err)
return
}
log.Println("Web server restarted successfully.")
subServer = sub.NewServer()
global.SetSubServer(subServer)
err = subServer.Start()
if err != nil {
log.Println(err)
log.Fatalf("Error restarting sub server: %v", err)
return
}
log.Println("Sub server restarted successfully.")
default:
server.Stop()
subServer.Stop()
log.Println("Shutting down servers.")
return
}
}
}
// resetSetting resets all panel settings to their default values.
func resetSetting() {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Failed to initialize database:", err)
return
}
settingService := service.SettingService{}
err = settingService.ResetSettings()
if err != nil {
fmt.Println("reset setting failed:", err)
fmt.Println("Failed to reset settings:", err)
} else {
fmt.Println("reset setting success")
fmt.Println("Settings successfully reset.")
}
}
// showSetting displays the current panel settings if show is true.
func showSetting(show bool) {
if show {
settingService := service.SettingService{}
@@ -133,53 +145,67 @@ func showSetting(show bool) {
fmt.Println("get webBasePath failed, error info:", err)
}
certFile, err := settingService.GetCertFile()
if err != nil {
fmt.Println("get cert file failed, error info:", err)
}
keyFile, err := settingService.GetKeyFile()
if err != nil {
fmt.Println("get key file failed, error info:", err)
}
userService := service.UserService{}
userModel, err := userService.GetFirstUser()
if err != nil {
fmt.Println("get current user info failed, error info:", err)
}
username := userModel.Username
userpasswd := userModel.Password
if username == "" || userpasswd == "" {
if userModel.Username == "" || userModel.Password == "" {
fmt.Println("current username or password is empty")
}
fmt.Println("current panel settings as follows:")
fmt.Println("username:", username)
fmt.Println("password:", userpasswd)
fmt.Println("port:", port)
if webBasePath != "" {
fmt.Println("webBasePath:", webBasePath)
if certFile == "" || keyFile == "" {
fmt.Println("Warning: Panel is not secure with SSL")
} else {
fmt.Println("webBasePath is not set")
fmt.Println("Panel is secure with SSL")
}
hasDefaultCredential := func() bool {
return userModel.Username == "admin" && crypto.CheckPasswordHash(userModel.Password, "admin")
}()
fmt.Println("hasDefaultCredential:", hasDefaultCredential)
fmt.Println("port:", port)
fmt.Println("webBasePath:", webBasePath)
}
}
// updateTgbotEnableSts enables or disables the Telegram bot notifications based on the status parameter.
func updateTgbotEnableSts(status bool) {
settingService := service.SettingService{}
currentTgSts, err := settingService.GetTgbotenabled()
currentTgSts, err := settingService.GetTgbotEnabled()
if err != nil {
fmt.Println(err)
return
}
logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
if currentTgSts != status {
err := settingService.SetTgbotenabled(status)
err := settingService.SetTgbotEnabled(status)
if err != nil {
fmt.Println(err)
return
} else {
logger.Infof("SetTgbotenabled[%v] success", status)
logger.Infof("SetTgbotEnabled[%v] success", status)
}
}
}
// updateTgbotSetting updates Telegram bot settings including token, chat ID, and runtime schedule.
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Error initializing database:", err)
return
}
@@ -188,72 +214,91 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
if tgBotToken != "" {
err := settingService.SetTgBotToken(tgBotToken)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot token: %v\n", err)
return
} else {
logger.Info("updateTgbotSetting tgBotToken success")
}
logger.Info("Successfully updated Telegram bot token.")
}
if tgBotRuntime != "" {
err := settingService.SetTgbotRuntime(tgBotRuntime)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot runtime: %v\n", err)
return
} else {
logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime)
}
logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime)
}
if tgBotChatid != "" {
err := settingService.SetTgBotChatId(tgBotChatid)
if err != nil {
fmt.Println(err)
fmt.Printf("Error setting Telegram bot chat ID: %v\n", err)
return
} else {
logger.Info("updateTgbotSetting tgBotChatid success")
}
logger.Info("Successfully updated Telegram bot chat ID.")
}
}
func updateSetting(port int, username string, password string, webBasePath string) {
// updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication.
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) {
err := database.InitDB(config.GetDBPath())
if err != nil {
fmt.Println(err)
fmt.Println("Database initialization failed:", err)
return
}
settingService := service.SettingService{}
userService := service.UserService{}
if port > 0 {
err := settingService.SetPort(port)
if err != nil {
fmt.Println("set port failed:", err)
fmt.Println("Failed to set port:", err)
} else {
fmt.Printf("set port %v success", port)
fmt.Printf("Port set successfully: %v\n", port)
}
}
if username != "" || password != "" {
userService := service.UserService{}
err := userService.UpdateFirstUser(username, password)
if err != nil {
fmt.Println("set username and password failed:", err)
fmt.Println("Failed to update username and password:", err)
} else {
fmt.Println("set username and password success")
fmt.Println("Username and password updated successfully")
}
}
if webBasePath != "" {
err := settingService.SetBasePath(webBasePath)
if err != nil {
fmt.Println("set base URI path failed:", err)
fmt.Println("Failed to set base URI path:", err)
} else {
fmt.Println("set base URI path success")
fmt.Println("Base URI path set successfully")
}
}
if resetTwoFactor {
err := settingService.SetTwoFactorEnable(false)
if err != nil {
fmt.Println("Failed to reset two-factor authentication:", err)
} else {
settingService.SetTwoFactorToken("")
fmt.Println("Two-factor authentication reset successfully")
}
}
if listenIP != "" {
err := settingService.SetListen(listenIP)
if err != nil {
fmt.Println("Failed to set listen IP:", err)
} else {
fmt.Printf("listen %v set successfully", listenIP)
}
}
}
// updateCert updates the SSL certificate files for the panel.
func updateCert(publicKey string, privateKey string) {
err := database.InitDB(config.GetDBPath())
if err != nil {
@@ -281,6 +326,40 @@ func updateCert(publicKey string, privateKey string) {
}
}
// GetCertificate displays the current SSL certificate settings if getCert is true.
func GetCertificate(getCert bool) {
if getCert {
settingService := service.SettingService{}
certFile, err := settingService.GetCertFile()
if err != nil {
fmt.Println("get cert file failed, error info:", err)
}
keyFile, err := settingService.GetKeyFile()
if err != nil {
fmt.Println("get key file failed, error info:", err)
}
fmt.Println("cert:", certFile)
fmt.Println("key:", keyFile)
}
}
// GetListenIP displays the current panel listen IP address if getListen is true.
func GetListenIP(getListen bool) {
if getListen {
settingService := service.SettingService{}
ListenIP, err := settingService.GetListen()
if err != nil {
log.Printf("Failed to retrieve listen IP: %v", err)
return
}
fmt.Println("listenIP:", ListenIP)
}
}
// migrateDb performs database migration operations for the 3x-ui panel.
func migrateDb() {
inboundService := service.InboundService{}
@@ -293,36 +372,8 @@ func migrateDb() {
fmt.Println("Migration done!")
}
func removeSecret() {
userService := service.UserService{}
secretExists, err := userService.CheckSecretExistence()
if err != nil {
fmt.Println("Error checking secret existence:", err)
return
}
if !secretExists {
fmt.Println("No secret exists to remove.")
return
}
err = userService.RemoveUserSecret()
if err != nil {
fmt.Println("Error removing secret:", err)
return
}
settingService := service.SettingService{}
err = settingService.SetSecretStatus(false)
if err != nil {
fmt.Println("Error updating secret status:", err)
return
}
fmt.Println("Secret removed successfully.")
}
// main is the entry point of the 3x-ui application.
// It parses command-line arguments to run the web server, migrate database, or update settings.
func main() {
if len(os.Args) < 2 {
runWebServer()
@@ -339,6 +390,8 @@ func main() {
var username string
var password string
var webBasePath string
var listenIP string
var getListen bool
var webCertFile string
var webKeyFile string
var tgbottoken string
@@ -347,20 +400,24 @@ func main() {
var tgbotRuntime string
var reset bool
var show bool
var remove_secret bool
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
settingCmd.BoolVar(&show, "show", false, "show current settings")
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "remove secret")
settingCmd.IntVar(&port, "port", 0, "set panel port")
settingCmd.StringVar(&username, "username", "", "set login username")
settingCmd.StringVar(&password, "password", "", "set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "set web base path")
settingCmd.StringVar(&webCertFile, "webCert", "", "set web public key path")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "set web private key path")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
var getCert bool
var resetTwoFactor bool
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
settingCmd.BoolVar(&show, "show", false, "Display current settings")
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
settingCmd.StringVar(&username, "username", "", "Set login username")
settingCmd.StringVar(&password, "password", "", "Set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
settingCmd.StringVar(&listenIP, "listenIP", "", "set panel listenIP IP")
settingCmd.BoolVar(&resetTwoFactor, "resetTwoFactor", false, "Reset two-factor authentication settings")
settingCmd.BoolVar(&getListen, "getListen", false, "Display current panel listenIP IP")
settingCmd.BoolVar(&getCert, "getCert", false, "Display current certificate settings")
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set cron time for Telegram bot notifications")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set chat ID for Telegram bot notifications")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable notifications via Telegram bot")
oldUsage := flag.Usage
flag.Usage = func() {
@@ -397,17 +454,20 @@ func main() {
if reset {
resetSetting()
} else {
updateSetting(port, username, password, webBasePath)
updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor)
}
if show {
showSetting(show)
}
if getListen {
GetListenIP(getListen)
}
if getCert {
GetCertificate(getCert)
}
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
}
if remove_secret {
removeSecret()
}
if enabletgbot {
updateTgbotEnableSts(enabletgbot)
}
@@ -422,7 +482,6 @@ func main() {
} else {
updateCert(webCertFile, webKeyFile)
}
default:
fmt.Println("Invalid subcommands")
fmt.Println()

BIN
media/01-overview-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

BIN
media/01-overview-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

BIN
media/02-inbounds-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

BIN
media/02-inbounds-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
media/05-settings-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

BIN
media/05-settings-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

BIN
media/06-configs-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
media/06-configs-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

BIN
media/07-bot-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
media/07-bot-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

View File

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

BIN
media/3x-ui-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

BIN
media/default-yellow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -13,7 +13,7 @@
"inbounds": [
{
"port": 10808,
"protocol": "socks",
"protocol": "mixed",
"settings": {
"auth": "noauth",
"udp": true,
@@ -23,11 +23,12 @@
"destOverride": [
"http",
"tls",
"quic",
"fakedns"
],
"enabled": true
},
"tag": "socks"
"tag": "mixed"
},
{
"port": 10809,
@@ -46,7 +47,9 @@
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
"domainStrategy": "AsIs",
"redirect": "",
"noises": []
}
},
{

View File

@@ -1,23 +1,47 @@
// Package sub provides subscription server functionality for the 3x-ui panel,
// including HTTP/HTTPS servers for serving subscription links and JSON configurations.
package sub
import (
"context"
"crypto/tls"
"html/template"
"io"
"io/fs"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"x-ui/config"
"x-ui/logger"
"x-ui/util/common"
"x-ui/web/middleware"
"x-ui/web/network"
"x-ui/web/service"
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/util/common"
webpkg "github.com/mhsanaei/3x-ui/v2/web"
"github.com/mhsanaei/3x-ui/v2/web/locale"
"github.com/mhsanaei/3x-ui/v2/web/middleware"
"github.com/mhsanaei/3x-ui/v2/web/network"
"github.com/mhsanaei/3x-ui/v2/web/service"
"github.com/gin-gonic/gin"
)
// setEmbeddedTemplates parses and sets embedded templates on the engine
func setEmbeddedTemplates(engine *gin.Engine) error {
t, err := template.New("").Funcs(engine.FuncMap).ParseFS(
webpkg.EmbeddedHTML(),
"html/common/page.html",
"html/component/aThemeSwitch.html",
"html/settings/panel/subscription/subpage.html",
)
if err != nil {
return err
}
engine.SetHTMLTemplate(t)
return nil
}
// Server represents the subscription server that serves subscription links and JSON configurations.
type Server struct {
httpServer *http.Server
listener net.Listener
@@ -29,6 +53,7 @@ type Server struct {
cancel context.CancelFunc
}
// NewServer creates a new subscription server instance with a cancellable context.
func NewServer() *Server {
ctx, cancel := context.WithCancel(context.Background())
return &Server{
@@ -37,14 +62,13 @@ func NewServer() *Server {
}
}
// initRouter configures the subscription server's Gin engine, middleware,
// templates and static assets and returns the ready-to-use engine.
func (s *Server) initRouter() (*gin.Engine, error) {
if config.IsDebug() {
gin.SetMode(gin.DebugMode)
} else {
gin.DefaultWriter = io.Discard
gin.DefaultErrorWriter = io.Discard
gin.SetMode(gin.ReleaseMode)
}
// Always run in release mode for the subscription server
gin.DefaultWriter = io.Discard
gin.DefaultErrorWriter = io.Discard
gin.SetMode(gin.ReleaseMode)
engine := gin.Default()
@@ -67,6 +91,23 @@ func (s *Server) initRouter() (*gin.Engine, error) {
return nil, err
}
// Determine if JSON subscription endpoint is enabled
subJsonEnable, err := s.settingService.GetSubJsonEnable()
if err != nil {
return nil, err
}
// Set base_path based on LinksPath for template rendering
// Ensure LinksPath ends with "/" for proper asset URL generation
basePath := LinksPath
if basePath != "/" && !strings.HasSuffix(basePath, "/") {
basePath += "/"
}
// logger.Debug("sub: Setting base_path to:", basePath)
engine.Use(func(c *gin.Context) {
c.Set("base_path", basePath)
})
Encrypt, err := s.settingService.GetSubEncrypt()
if err != nil {
return nil, err
@@ -92,6 +133,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
SubJsonFragment = ""
}
SubJsonNoises, err := s.settingService.GetSubJsonNoises()
if err != nil {
SubJsonNoises = ""
}
SubJsonMux, err := s.settingService.GetSubJsonMux()
if err != nil {
SubJsonMux = ""
@@ -102,15 +148,119 @@ func (s *Server) initRouter() (*gin.Engine, error) {
SubJsonRules = ""
}
SubTitle, err := s.settingService.GetSubTitle()
if err != nil {
SubTitle = ""
}
// set per-request localizer from headers/cookies
engine.Use(locale.LocalizerMiddleware())
// register i18n function similar to web server
i18nWebFunc := func(key string, params ...string) string {
return locale.I18n(locale.Web, key, params...)
}
engine.SetFuncMap(map[string]any{"i18n": i18nWebFunc})
// Templates: prefer embedded; fallback to disk if necessary
if err := setEmbeddedTemplates(engine); err != nil {
logger.Warning("sub: failed to parse embedded templates:", err)
if files, derr := s.getHtmlFiles(); derr == nil {
engine.LoadHTMLFiles(files...)
} else {
logger.Error("sub: no templates available (embedded parse and disk load failed)", err, derr)
}
}
// Assets: use disk if present, fallback to embedded
// Serve under both root (/assets) and under the subscription path prefix (LinksPath + "assets")
// so reverse proxies with a URI prefix can load assets correctly.
// Determine LinksPath earlier to compute prefixed assets mount.
// Note: LinksPath always starts and ends with "/" (validated in settings).
var linksPathForAssets string
if LinksPath == "/" {
linksPathForAssets = "/assets"
} else {
// ensure single slash join
linksPathForAssets = strings.TrimRight(LinksPath, "/") + "/assets"
}
// Mount assets in multiple paths to handle different URL patterns
var assetsFS http.FileSystem
if _, err := os.Stat("web/assets"); err == nil {
assetsFS = http.FS(os.DirFS("web/assets"))
} else {
if subFS, err := fs.Sub(webpkg.EmbeddedAssets(), "assets"); err == nil {
assetsFS = http.FS(subFS)
} else {
logger.Error("sub: failed to mount embedded assets:", err)
}
}
if assetsFS != nil {
engine.StaticFS("/assets", assetsFS)
if linksPathForAssets != "/assets" {
engine.StaticFS(linksPathForAssets, assetsFS)
}
// Add middleware to handle dynamic asset paths with subid
if LinksPath != "/" {
engine.Use(func(c *gin.Context) {
path := c.Request.URL.Path
// Check if this is an asset request with subid pattern: /sub/path/{subid}/assets/...
pathPrefix := strings.TrimRight(LinksPath, "/") + "/"
if strings.HasPrefix(path, pathPrefix) && strings.Contains(path, "/assets/") {
// Extract the asset path after /assets/
assetsIndex := strings.Index(path, "/assets/")
if assetsIndex != -1 {
assetPath := path[assetsIndex+8:] // +8 to skip "/assets/"
if assetPath != "" {
// Serve the asset file
c.FileFromFS(assetPath, assetsFS)
c.Abort()
return
}
}
}
c.Next()
})
}
}
g := engine.Group("/")
s.sub = NewSUBController(
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
SubJsonFragment, SubJsonMux, SubJsonRules)
g, LinksPath, JsonPath, subJsonEnable, Encrypt, ShowInfo, RemarkModel, SubUpdates,
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle)
return engine, nil
}
// getHtmlFiles loads templates from local folder (used in debug mode)
func (s *Server) getHtmlFiles() ([]string, error) {
dir, _ := os.Getwd()
files := []string{}
// common layout
common := filepath.Join(dir, "web", "html", "common", "page.html")
if _, err := os.Stat(common); err == nil {
files = append(files, common)
}
// components used
theme := filepath.Join(dir, "web", "html", "component", "aThemeSwitch.html")
if _, err := os.Stat(theme); err == nil {
files = append(files, theme)
}
// page itself
page := filepath.Join(dir, "web", "html", "subpage.html")
if _, err := os.Stat(page); err == nil {
files = append(files, page)
} else {
return nil, err
}
return files, nil
}
// Start initializes and starts the subscription server with configured settings.
func (s *Server) Start() (err error) {
// This is an anonymous function, no function name
defer func() {
@@ -163,13 +313,13 @@ func (s *Server) Start() (err error) {
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("sub server run https on", listener.Addr())
logger.Info("Sub server running HTTPS on", listener.Addr())
} else {
logger.Error("error in loading certificates: ", err)
logger.Info("sub server run http on", listener.Addr())
logger.Error("Error loading certificates:", err)
logger.Info("Sub server running HTTP on", listener.Addr())
}
} else {
logger.Info("sub server run http on", listener.Addr())
logger.Info("Sub server running HTTP on", listener.Addr())
}
s.listener = listener
@@ -184,6 +334,7 @@ func (s *Server) Start() (err error) {
return nil
}
// Stop gracefully shuts down the subscription server and closes the listener.
func (s *Server) Stop() error {
s.cancel()
@@ -198,6 +349,7 @@ func (s *Server) Stop() error {
return common.Combine(err1, err2)
}
// GetCtx returns the server's context for cancellation and deadline management.
func (s *Server) GetCtx() context.Context {
return s.ctx
}

View File

@@ -2,14 +2,20 @@ package sub
import (
"encoding/base64"
"net"
"fmt"
"strings"
"github.com/mhsanaei/3x-ui/v2/config"
"github.com/gin-gonic/gin"
)
// SUBController handles HTTP requests for subscription links and JSON configurations.
type SUBController struct {
subTitle string
subPath string
subJsonPath string
jsonEnabled bool
subEncrypt bool
updateInterval string
@@ -17,55 +23,54 @@ type SUBController struct {
subJsonService *SubJsonService
}
// NewSUBController creates a new subscription controller with the given configuration.
func NewSUBController(
g *gin.RouterGroup,
subPath string,
jsonPath string,
jsonEnabled bool,
encrypt bool,
showInfo bool,
rModel string,
update string,
jsonFragment string,
jsonNoise string,
jsonMux string,
jsonRules string,
subTitle string,
) *SUBController {
sub := NewSubService(showInfo, rModel)
a := &SUBController{
subTitle: subTitle,
subPath: subPath,
subJsonPath: jsonPath,
jsonEnabled: jsonEnabled,
subEncrypt: encrypt,
updateInterval: update,
subService: sub,
subJsonService: NewSubJsonService(jsonFragment, jsonMux, jsonRules, sub),
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
}
a.initRouter(g)
return a
}
// initRouter registers HTTP routes for subscription links and JSON endpoints
// on the provided router group.
func (a *SUBController) initRouter(g *gin.RouterGroup) {
gLink := g.Group(a.subPath)
gJson := g.Group(a.subJsonPath)
gLink.GET(":subid", a.subs)
gJson.GET(":subid", a.subJsons)
if a.jsonEnabled {
gJson := g.Group(a.subJsonPath)
gJson.GET(":subid", a.subJsons)
}
}
// subs handles HTTP requests for subscription links, returning either HTML page or base64-encoded subscription data.
func (a *SUBController) subs(c *gin.Context) {
subId := c.Param("subid")
host := c.GetHeader("X-Forwarded-Host")
if host == "" {
host = c.GetHeader("X-Real-IP")
}
if host == "" {
var err error
host, _, err = net.SplitHostPort(c.Request.Host)
if err != nil {
host = c.Request.Host
}
}
subs, header, err := a.subService.GetSubs(subId, host)
scheme, host, hostWithPort, hostHeader := a.subService.ResolveRequest(c)
subs, lastOnline, traffic, err := a.subService.GetSubs(subId, host)
if err != nil || len(subs) == 0 {
c.String(400, "Error!")
} else {
@@ -74,10 +79,55 @@ func (a *SUBController) subs(c *gin.Context) {
result += sub + "\n"
}
// If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here
accept := c.GetHeader("Accept")
if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") {
// Build page data in service
subURL, subJsonURL := a.subService.BuildURLs(scheme, hostWithPort, a.subPath, a.subJsonPath, subId)
if !a.jsonEnabled {
subJsonURL = ""
}
// Get base_path from context (set by middleware)
basePath, exists := c.Get("base_path")
if !exists {
basePath = "/"
}
// Add subId to base_path for asset URLs
basePathStr := basePath.(string)
if basePathStr == "/" {
basePathStr = "/" + subId + "/"
} else {
// Remove trailing slash if exists, add subId, then add trailing slash
basePathStr = strings.TrimRight(basePathStr, "/") + "/" + subId + "/"
}
page := a.subService.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, subURL, subJsonURL, basePathStr)
c.HTML(200, "subpage.html", gin.H{
"title": "subscription.title",
"cur_ver": config.GetVersion(),
"host": page.Host,
"base_path": page.BasePath,
"sId": page.SId,
"download": page.Download,
"upload": page.Upload,
"total": page.Total,
"used": page.Used,
"remained": page.Remained,
"expire": page.Expire,
"lastOnline": page.LastOnline,
"datepicker": page.Datepicker,
"downloadByte": page.DownloadByte,
"uploadByte": page.UploadByte,
"totalByte": page.TotalByte,
"subUrl": page.SubUrl,
"subJsonUrl": page.SubJsonUrl,
"result": page.Result,
})
return
}
// Add headers
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
c.Writer.Header().Set("Profile-Title", subId)
header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
if a.subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
@@ -87,29 +137,25 @@ func (a *SUBController) subs(c *gin.Context) {
}
}
// subJsons handles HTTP requests for JSON subscription configurations.
func (a *SUBController) subJsons(c *gin.Context) {
subId := c.Param("subid")
host := c.GetHeader("X-Forwarded-Host")
if host == "" {
host = c.GetHeader("X-Real-IP")
}
if host == "" {
var err error
host, _, err = net.SplitHostPort(c.Request.Host)
if err != nil {
host = c.Request.Host
}
}
_, host, _, _ := a.subService.ResolveRequest(c)
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
if err != nil || len(jsonSub) == 0 {
c.String(400, "Error!")
} else {
// Add headers
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
c.Writer.Header().Set("Profile-Title", subId)
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
c.String(200, jsonSub)
}
}
// ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title.
func (a *SUBController) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) {
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", updateInterval)
c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle)))
}

View File

@@ -6,32 +6,35 @@ import (
"fmt"
"strings"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/json_util"
"x-ui/util/random"
"x-ui/web/service"
"x-ui/xray"
"github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/util/json_util"
"github.com/mhsanaei/3x-ui/v2/util/random"
"github.com/mhsanaei/3x-ui/v2/web/service"
"github.com/mhsanaei/3x-ui/v2/xray"
)
//go:embed default.json
var defaultJson string
// SubJsonService handles JSON subscription configuration generation and management.
type SubJsonService struct {
configJson map[string]interface{}
configJson map[string]any
defaultOutbounds []json_util.RawMessage
fragment string
noises string
mux string
inboundService service.InboundService
SubService *SubService
}
func NewSubJsonService(fragment string, mux string, rules string, subService *SubService) *SubJsonService {
var configJson map[string]interface{}
// NewSubJsonService creates a new JSON subscription service with the given configuration.
func NewSubJsonService(fragment string, noises string, mux string, rules string, subService *SubService) *SubJsonService {
var configJson map[string]any
var defaultOutbounds []json_util.RawMessage
json.Unmarshal([]byte(defaultJson), &configJson)
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
if outboundSlices, ok := configJson["outbounds"].([]any); ok {
for _, defaultOutbound := range outboundSlices {
jsonBytes, _ := json.Marshal(defaultOutbound)
defaultOutbounds = append(defaultOutbounds, jsonBytes)
@@ -39,9 +42,9 @@ func NewSubJsonService(fragment string, mux string, rules string, subService *Su
}
if rules != "" {
var newRules []interface{}
routing, _ := configJson["routing"].(map[string]interface{})
defaultRules, _ := routing["rules"].([]interface{})
var newRules []any
routing, _ := configJson["routing"].(map[string]any)
defaultRules, _ := routing["rules"].([]any)
json.Unmarshal([]byte(rules), &newRules)
defaultRules = append(newRules, defaultRules...)
routing["rules"] = defaultRules
@@ -52,15 +55,21 @@ func NewSubJsonService(fragment string, mux string, rules string, subService *Su
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
}
if noises != "" {
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(noises))
}
return &SubJsonService{
configJson: configJson,
defaultOutbounds: defaultOutbounds,
fragment: fragment,
noises: noises,
mux: mux,
SubService: subService,
}
}
// GetJson generates a JSON subscription configuration for the given subscription ID and host.
func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
inbounds, err := s.SubService.getInboundsBySubId(subId)
if err != nil || len(inbounds) == 0 {
@@ -142,10 +151,10 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
var newJsonArray []json_util.RawMessage
stream := s.streamData(inbound.StreamSettings)
externalProxies, ok := stream["externalProxy"].([]interface{})
externalProxies, ok := stream["externalProxy"].([]any)
if !ok || len(externalProxies) == 0 {
externalProxies = []interface{}{
map[string]interface{}{
externalProxies = []any{
map[string]any{
"forceTls": "same",
"dest": host,
"port": float64(inbound.Port),
@@ -157,7 +166,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
delete(stream, "externalProxy")
for _, ep := range externalProxies {
extPrxy := ep.(map[string]interface{})
extPrxy := ep.(map[string]any)
inbound.Listen = extPrxy["dest"].(string)
inbound.Port = int(extPrxy["port"].(float64))
newStream := stream
@@ -165,12 +174,12 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
case "tls":
if newStream["security"] != "tls" {
newStream["security"] = "tls"
newStream["tslSettings"] = map[string]interface{}{}
newStream["tlsSettings"] = map[string]any{}
}
case "none":
if newStream["security"] != "none" {
newStream["security"] = "none"
delete(newStream, "tslSettings")
delete(newStream, "tlsSettings")
}
}
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
@@ -178,14 +187,16 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
var newOutbounds []json_util.RawMessage
switch inbound.Protocol {
case "vmess", "vless":
case "vmess":
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
case "vless":
newOutbounds = append(newOutbounds, s.genVless(inbound, streamSettings, client))
case "trojan", "shadowsocks":
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
}
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
newConfigJson := make(map[string]interface{})
newConfigJson := make(map[string]any)
for key, value := range s.configJson {
newConfigJson[key] = value
}
@@ -199,19 +210,20 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
return newJsonArray
}
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
var streamSettings map[string]interface{}
func (s *SubJsonService) streamData(stream string) map[string]any {
var streamSettings map[string]any
json.Unmarshal([]byte(stream), &streamSettings)
security, _ := streamSettings["security"].(string)
if security == "tls" {
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
} else if security == "reality" {
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
switch security {
case "tls":
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
case "reality":
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
}
delete(streamSettings, "sockopt")
if s.fragment != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpMptcp": true, "tcpNoDelay": true}`)
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpMptcp": true, "penetrate": true}`)
}
// remove proxy protocol
@@ -227,17 +239,17 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
return streamSettings
}
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
netSettings, ok := setting.(map[string]interface{})
func (s *SubJsonService) removeAcceptProxy(setting any) map[string]any {
netSettings, ok := setting.(map[string]any)
if ok {
delete(netSettings, "acceptProxyProtocol")
}
return netSettings
}
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
tlsData := make(map[string]interface{}, 1)
tlsClientSettings, _ := tData["settings"].(map[string]interface{})
func (s *SubJsonService) tlsData(tData map[string]any) map[string]any {
tlsData := make(map[string]any, 1)
tlsClientSettings, _ := tData["settings"].(map[string]any)
tlsData["serverName"] = tData["serverName"]
tlsData["alpn"] = tData["alpn"]
@@ -250,23 +262,24 @@ func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interf
return tlsData
}
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
rltyData := make(map[string]interface{}, 1)
rltyClientSettings, _ := rData["settings"].(map[string]interface{})
func (s *SubJsonService) realityData(rData map[string]any) map[string]any {
rltyData := make(map[string]any, 1)
rltyClientSettings, _ := rData["settings"].(map[string]any)
rltyData["show"] = false
rltyData["publicKey"] = rltyClientSettings["publicKey"]
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
rltyData["mldsa65Verify"] = rltyClientSettings["mldsa65Verify"]
// Set random data
rltyData["spiderX"] = "/" + random.Seq(15)
shortIds, ok := rData["shortIds"].([]interface{})
shortIds, ok := rData["shortIds"].([]any)
if ok && len(shortIds) > 0 {
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
} else {
rltyData["shortId"] = ""
}
serverNames, ok := rData["serverNames"].([]interface{})
serverNames, ok := rData["serverNames"].([]any)
if ok && len(serverNames) > 0 {
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
} else {
@@ -281,12 +294,8 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_ut
usersData := make([]UserVnext, 1)
usersData[0].ID = client.ID
usersData[0].Level = 8
if inbound.Protocol == model.VLESS {
usersData[0].Flow = client.Flow
usersData[0].Encryption = "none"
}
usersData[0].Email = client.Email
usersData[0].Security = client.Security
vnextData := make([]VnextSetting, 1)
vnextData[0] = VnextSetting{
Address: inbound.Listen,
@@ -300,14 +309,42 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_ut
outbound.Mux = json_util.RawMessage(s.mux)
}
outbound.StreamSettings = streamSettings
outbound.Settings = OutboundSettings{
Vnext: vnextData,
outbound.Settings = map[string]any{
"vnext": vnextData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
return result
}
func (s *SubJsonService) genVless(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
outbound := Outbound{}
outbound.Protocol = string(inbound.Protocol)
outbound.Tag = "proxy"
if s.mux != "" {
outbound.Mux = json_util.RawMessage(s.mux)
}
outbound.StreamSettings = streamSettings
settings := make(map[string]any)
settings["address"] = inbound.Listen
settings["port"] = inbound.Port
settings["id"] = client.ID
if client.Flow != "" {
settings["flow"] = client.Flow
}
// Add encryption for VLESS outbound from inbound settings
var inboundSettings map[string]any
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
if encryption, ok := inboundSettings["encryption"].(string); ok {
settings["encryption"] = encryption
}
outbound.Settings = settings
result, _ := json.MarshalIndent(outbound, "", " ")
return result
}
func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
outbound := Outbound{}
@@ -320,7 +357,7 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
}
if inbound.Protocol == model.Shadowsocks {
var inboundSettings map[string]interface{}
var inboundSettings map[string]any
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
method, _ := inboundSettings["method"].(string)
serverData[0].Method = method
@@ -339,8 +376,8 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
outbound.Mux = json_util.RawMessage(s.mux)
}
outbound.StreamSettings = streamSettings
outbound.Settings = OutboundSettings{
Servers: serverData,
outbound.Settings = map[string]any{
"servers": serverData,
}
result, _ := json.MarshalIndent(outbound, "", " ")
@@ -348,17 +385,11 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
}
type Outbound struct {
Protocol string `json:"protocol"`
Tag string `json:"tag"`
StreamSettings json_util.RawMessage `json:"streamSettings"`
Mux json_util.RawMessage `json:"mux,omitempty"`
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
Settings OutboundSettings `json:"settings,omitempty"`
}
type OutboundSettings struct {
Vnext []VnextSetting `json:"vnext,omitempty"`
Servers []ServerSetting `json:"servers,omitempty"`
Protocol string `json:"protocol"`
Tag string `json:"tag"`
StreamSettings json_util.RawMessage `json:"streamSettings"`
Mux json_util.RawMessage `json:"mux,omitempty"`
Settings map[string]any `json:"settings,omitempty"`
}
type VnextSetting struct {
@@ -368,10 +399,9 @@ type VnextSetting struct {
}
type UserVnext struct {
Encryption string `json:"encryption,omitempty"`
Flow string `json:"flow,omitempty"`
ID string `json:"id"`
Level int `json:"level"`
ID string `json:"id"`
Email string `json:"email,omitempty"`
Security string `json:"security,omitempty"`
}
type ServerSetting struct {

View File

@@ -3,21 +3,24 @@ package sub
import (
"encoding/base64"
"fmt"
"net"
"net/url"
"strings"
"time"
"x-ui/database"
"x-ui/database/model"
"x-ui/logger"
"x-ui/util/common"
"x-ui/util/random"
"x-ui/web/service"
"x-ui/xray"
"github.com/gin-gonic/gin"
"github.com/goccy/go-json"
"github.com/mhsanaei/3x-ui/v2/database"
"github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/util/common"
"github.com/mhsanaei/3x-ui/v2/util/random"
"github.com/mhsanaei/3x-ui/v2/web/service"
"github.com/mhsanaei/3x-ui/v2/xray"
)
// SubService provides business logic for generating subscription links and managing subscription data.
type SubService struct {
address string
showInfo bool
@@ -27,6 +30,7 @@ type SubService struct {
settingService service.SettingService
}
// NewSubService creates a new subscription service with the given configuration.
func NewSubService(showInfo bool, remarkModel string) *SubService {
return &SubService{
showInfo: showInfo,
@@ -34,19 +38,20 @@ func NewSubService(showInfo bool, remarkModel string) *SubService {
}
}
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
// GetSubs retrieves subscription links for a given subscription ID and host.
func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) {
s.address = host
var result []string
var header string
var traffic xray.ClientTraffic
var lastOnline int64
var clientTraffics []xray.ClientTraffic
inbounds, err := s.getInboundsBySubId(subId)
if err != nil {
return nil, "", err
return nil, 0, traffic, err
}
if len(inbounds) == 0 {
return nil, "", common.NewError("No inbounds found with ", subId)
return nil, 0, traffic, common.NewError("No inbounds found with ", subId)
}
s.datepicker, err = s.settingService.GetDatepicker()
@@ -73,7 +78,11 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
if client.Enable && client.SubID == subId {
link := s.getLink(inbound, client.Email)
result = append(result, link)
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
ct := s.getClientTraffics(inbound.ClientStats, client.Email)
clientTraffics = append(clientTraffics, ct)
if ct.LastOnline > lastOnline {
lastOnline = ct.LastOnline
}
}
}
}
@@ -100,8 +109,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
}
}
}
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
return result, header, nil
return result, lastOnline, traffic, nil
}
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
@@ -141,9 +149,9 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
return "", 0, "", err
}
var stream map[string]interface{}
var stream map[string]any
json.Unmarshal([]byte(streamSettings), &stream)
var masterStream map[string]interface{}
var masterStream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
stream["security"] = masterStream["security"]
stream["tlsSettings"] = masterStream["tlsSettings"]
@@ -168,88 +176,78 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string {
}
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
if inbound.Protocol != model.VMess {
if inbound.Protocol != model.VMESS {
return ""
}
obj := map[string]interface{}{
obj := map[string]any{
"v": "2",
"add": s.address,
"port": inbound.Port,
"type": "none",
}
var stream map[string]interface{}
var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
network, _ := stream["network"].(string)
obj["net"] = network
switch network {
case "tcp":
tcp, _ := stream["tcpSettings"].(map[string]interface{})
header, _ := tcp["header"].(map[string]interface{})
tcp, _ := stream["tcpSettings"].(map[string]any)
header, _ := tcp["header"].(map[string]any)
typeStr, _ := header["type"].(string)
obj["type"] = typeStr
if typeStr == "http" {
request := header["request"].(map[string]interface{})
requestPath, _ := request["path"].([]interface{})
request := header["request"].(map[string]any)
requestPath, _ := request["path"].([]any)
obj["path"] = requestPath[0].(string)
headers, _ := request["headers"].(map[string]interface{})
headers, _ := request["headers"].(map[string]any)
obj["host"] = searchHost(headers)
}
case "kcp":
kcp, _ := stream["kcpSettings"].(map[string]interface{})
header, _ := kcp["header"].(map[string]interface{})
kcp, _ := stream["kcpSettings"].(map[string]any)
header, _ := kcp["header"].(map[string]any)
obj["type"], _ = header["type"].(string)
obj["path"], _ = kcp["seed"].(string)
case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{})
ws, _ := stream["wsSettings"].(map[string]any)
obj["path"] = ws["path"].(string)
if host, ok := ws["host"].(string); ok && len(host) > 0 {
obj["host"] = host
} else {
headers, _ := ws["headers"].(map[string]interface{})
headers, _ := ws["headers"].(map[string]any)
obj["host"] = searchHost(headers)
}
case "http":
obj["net"] = "h2"
http, _ := stream["httpSettings"].(map[string]interface{})
obj["path"], _ = http["path"].(string)
obj["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
header := quic["header"].(map[string]interface{})
obj["type"], _ = header["type"].(string)
obj["host"], _ = quic["security"].(string)
obj["path"], _ = quic["key"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
grpc, _ := stream["grpcSettings"].(map[string]any)
obj["path"] = grpc["serviceName"].(string)
obj["authority"] = grpc["authority"].(string)
if grpc["multiMode"].(bool) {
obj["type"] = "multi"
}
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
obj["path"] = httpupgrade["path"].(string)
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
obj["host"] = host
} else {
headers, _ := httpupgrade["headers"].(map[string]interface{})
headers, _ := httpupgrade["headers"].(map[string]any)
obj["host"] = searchHost(headers)
}
case "splithttp":
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
obj["path"] = splithttp["path"].(string)
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
case "xhttp":
xhttp, _ := stream["xhttpSettings"].(map[string]any)
obj["path"] = xhttp["path"].(string)
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
obj["host"] = host
} else {
headers, _ := splithttp["headers"].(map[string]interface{})
headers, _ := xhttp["headers"].(map[string]any)
obj["host"] = searchHost(headers)
}
obj["mode"] = xhttp["mode"].(string)
}
security, _ := stream["security"].(string)
obj["tls"] = security
if security == "tls" {
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
alpns, _ := tlsSetting["alpn"].([]any)
if len(alpns) > 0 {
var alpn []string
for _, a := range alpns {
@@ -281,15 +279,16 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
}
}
obj["id"] = clients[clientIndex].ID
obj["scy"] = clients[clientIndex].Security
externalProxies, _ := stream["externalProxy"].([]interface{})
externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
ep, _ := externalProxy.(map[string]any)
newSecurity, _ := ep["forceTls"].(string)
newObj := map[string]interface{}{}
newObj := map[string]any{}
for key, value := range obj {
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
newObj[key] = value
@@ -322,7 +321,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if inbound.Protocol != model.VLESS {
return ""
}
var stream map[string]interface{}
var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
@@ -338,74 +337,72 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params := make(map[string]string)
params["type"] = streamNetwork
// Add encryption parameter for VLESS from inbound settings
var settings map[string]any
json.Unmarshal([]byte(inbound.Settings), &settings)
if encryption, ok := settings["encryption"].(string); ok {
params["encryption"] = encryption
}
switch streamNetwork {
case "tcp":
tcp, _ := stream["tcpSettings"].(map[string]interface{})
header, _ := tcp["header"].(map[string]interface{})
tcp, _ := stream["tcpSettings"].(map[string]any)
header, _ := tcp["header"].(map[string]any)
typeStr, _ := header["type"].(string)
if typeStr == "http" {
request := header["request"].(map[string]interface{})
requestPath, _ := request["path"].([]interface{})
request := header["request"].(map[string]any)
requestPath, _ := request["path"].([]any)
params["path"] = requestPath[0].(string)
headers, _ := request["headers"].(map[string]interface{})
headers, _ := request["headers"].(map[string]any)
params["host"] = searchHost(headers)
params["headerType"] = "http"
}
case "kcp":
kcp, _ := stream["kcpSettings"].(map[string]interface{})
header, _ := kcp["header"].(map[string]interface{})
kcp, _ := stream["kcpSettings"].(map[string]any)
header, _ := kcp["header"].(map[string]any)
params["headerType"] = header["type"].(string)
params["seed"] = kcp["seed"].(string)
case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{})
ws, _ := stream["wsSettings"].(map[string]any)
params["path"] = ws["path"].(string)
if host, ok := ws["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := ws["headers"].(map[string]interface{})
headers, _ := ws["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
case "http":
http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string)
params["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
params["quicSecurity"] = quic["security"].(string)
params["key"] = quic["key"].(string)
header := quic["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
grpc, _ := stream["grpcSettings"].(map[string]any)
params["serviceName"] = grpc["serviceName"].(string)
params["authority"], _ = grpc["authority"].(string)
if grpc["multiMode"].(bool) {
params["mode"] = "multi"
}
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
params["path"] = httpupgrade["path"].(string)
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := httpupgrade["headers"].(map[string]interface{})
headers, _ := httpupgrade["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
case "splithttp":
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
params["path"] = splithttp["path"].(string)
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
case "xhttp":
xhttp, _ := stream["xhttpSettings"].(map[string]any)
params["path"] = xhttp["path"].(string)
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := splithttp["headers"].(map[string]interface{})
headers, _ := xhttp["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
params["mode"] = xhttp["mode"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
alpns, _ := tlsSetting["alpn"].([]any)
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
@@ -436,18 +433,18 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if security == "reality" {
params["security"] = "reality"
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
realitySetting, _ := stream["realitySettings"].(map[string]any)
realitySettings, _ := searchKey(realitySetting, "settings")
if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{})
sNames, _ := sniValue.([]any)
params["sni"] = sNames[random.Num(len(sNames))].(string)
}
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string)
}
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{})
shortIds, _ := sidValue.([]any)
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
}
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
@@ -455,6 +452,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["fp"] = fp
}
}
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
params["pqv"] = pqv
}
}
params["spx"] = "/" + random.Seq(15)
}
@@ -463,47 +465,16 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
}
}
if security == "xtls" {
params["security"] = "xtls"
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
alpns, _ := xtlsSetting["alpn"].([]interface{})
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil {
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
if insecure.(bool) {
params["allowInsecure"] = "1"
}
}
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
}
if security != "tls" && security != "reality" && security != "xtls" {
if security != "tls" && security != "reality" {
params["security"] = "none"
}
externalProxies, _ := stream["externalProxy"].([]interface{})
externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
ep, _ := externalProxy.(map[string]any)
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
@@ -556,7 +527,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if inbound.Protocol != model.Trojan {
return ""
}
var stream map[string]interface{}
var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1
@@ -574,72 +545,63 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
switch streamNetwork {
case "tcp":
tcp, _ := stream["tcpSettings"].(map[string]interface{})
header, _ := tcp["header"].(map[string]interface{})
tcp, _ := stream["tcpSettings"].(map[string]any)
header, _ := tcp["header"].(map[string]any)
typeStr, _ := header["type"].(string)
if typeStr == "http" {
request := header["request"].(map[string]interface{})
requestPath, _ := request["path"].([]interface{})
request := header["request"].(map[string]any)
requestPath, _ := request["path"].([]any)
params["path"] = requestPath[0].(string)
headers, _ := request["headers"].(map[string]interface{})
headers, _ := request["headers"].(map[string]any)
params["host"] = searchHost(headers)
params["headerType"] = "http"
}
case "kcp":
kcp, _ := stream["kcpSettings"].(map[string]interface{})
header, _ := kcp["header"].(map[string]interface{})
kcp, _ := stream["kcpSettings"].(map[string]any)
header, _ := kcp["header"].(map[string]any)
params["headerType"] = header["type"].(string)
params["seed"] = kcp["seed"].(string)
case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{})
ws, _ := stream["wsSettings"].(map[string]any)
params["path"] = ws["path"].(string)
if host, ok := ws["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := ws["headers"].(map[string]interface{})
headers, _ := ws["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
case "http":
http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string)
params["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
params["quicSecurity"] = quic["security"].(string)
params["key"] = quic["key"].(string)
header := quic["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
grpc, _ := stream["grpcSettings"].(map[string]any)
params["serviceName"] = grpc["serviceName"].(string)
params["authority"], _ = grpc["authority"].(string)
if grpc["multiMode"].(bool) {
params["mode"] = "multi"
}
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
params["path"] = httpupgrade["path"].(string)
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := httpupgrade["headers"].(map[string]interface{})
headers, _ := httpupgrade["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
case "splithttp":
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
params["path"] = splithttp["path"].(string)
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
case "xhttp":
xhttp, _ := stream["xhttpSettings"].(map[string]any)
params["path"] = xhttp["path"].(string)
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := splithttp["headers"].(map[string]interface{})
headers, _ := xhttp["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
params["mode"] = xhttp["mode"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
alpns, _ := tlsSetting["alpn"].([]any)
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
@@ -666,18 +628,18 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if security == "reality" {
params["security"] = "reality"
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
realitySetting, _ := stream["realitySettings"].(map[string]any)
realitySettings, _ := searchKey(realitySetting, "settings")
if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{})
sNames, _ := sniValue.([]any)
params["sni"] = sNames[random.Num(len(sNames))].(string)
}
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string)
}
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{})
shortIds, _ := sidValue.([]any)
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
}
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
@@ -685,6 +647,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["fp"] = fp
}
}
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
params["pqv"] = pqv
}
}
params["spx"] = "/" + random.Seq(15)
}
@@ -693,48 +660,16 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
}
}
if security == "xtls" {
params["security"] = "xtls"
xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{})
alpns, _ := xtlsSetting["alpn"].([]interface{})
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
}
if len(alpn) > 0 {
params["alpn"] = strings.Join(alpn, ",")
}
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
params["sni"], _ = sniValue.(string)
}
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
if xtlsSetting != nil {
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
params["fp"], _ = fpValue.(string)
}
if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok {
if insecure.(bool) {
params["allowInsecure"] = "1"
}
}
}
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
params["flow"] = clients[clientIndex].Flow
}
}
if security != "tls" && security != "reality" && security != "xtls" {
if security != "tls" && security != "reality" {
params["security"] = "none"
}
externalProxies, _ := stream["externalProxy"].([]interface{})
externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
ep, _ := externalProxy.(map[string]any)
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
@@ -788,11 +723,11 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
if inbound.Protocol != model.Shadowsocks {
return ""
}
var stream map[string]interface{}
var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound)
var settings map[string]interface{}
var settings map[string]any
json.Unmarshal([]byte(inbound.Settings), &settings)
inboundPassword := settings["password"].(string)
method := settings["method"].(string)
@@ -809,73 +744,64 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
switch streamNetwork {
case "tcp":
tcp, _ := stream["tcpSettings"].(map[string]interface{})
header, _ := tcp["header"].(map[string]interface{})
tcp, _ := stream["tcpSettings"].(map[string]any)
header, _ := tcp["header"].(map[string]any)
typeStr, _ := header["type"].(string)
if typeStr == "http" {
request := header["request"].(map[string]interface{})
requestPath, _ := request["path"].([]interface{})
request := header["request"].(map[string]any)
requestPath, _ := request["path"].([]any)
params["path"] = requestPath[0].(string)
headers, _ := request["headers"].(map[string]interface{})
headers, _ := request["headers"].(map[string]any)
params["host"] = searchHost(headers)
params["headerType"] = "http"
}
case "kcp":
kcp, _ := stream["kcpSettings"].(map[string]interface{})
header, _ := kcp["header"].(map[string]interface{})
kcp, _ := stream["kcpSettings"].(map[string]any)
header, _ := kcp["header"].(map[string]any)
params["headerType"] = header["type"].(string)
params["seed"] = kcp["seed"].(string)
case "ws":
ws, _ := stream["wsSettings"].(map[string]interface{})
ws, _ := stream["wsSettings"].(map[string]any)
params["path"] = ws["path"].(string)
if host, ok := ws["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := ws["headers"].(map[string]interface{})
headers, _ := ws["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
case "http":
http, _ := stream["httpSettings"].(map[string]interface{})
params["path"] = http["path"].(string)
params["host"] = searchHost(http)
case "quic":
quic, _ := stream["quicSettings"].(map[string]interface{})
params["quicSecurity"] = quic["security"].(string)
params["key"] = quic["key"].(string)
header := quic["header"].(map[string]interface{})
params["headerType"] = header["type"].(string)
case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{})
grpc, _ := stream["grpcSettings"].(map[string]any)
params["serviceName"] = grpc["serviceName"].(string)
params["authority"], _ = grpc["authority"].(string)
if grpc["multiMode"].(bool) {
params["mode"] = "multi"
}
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
params["path"] = httpupgrade["path"].(string)
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := httpupgrade["headers"].(map[string]interface{})
headers, _ := httpupgrade["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
case "splithttp":
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
params["path"] = splithttp["path"].(string)
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
case "xhttp":
xhttp, _ := stream["xhttpSettings"].(map[string]any)
params["path"] = xhttp["path"].(string)
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
params["host"] = host
} else {
headers, _ := splithttp["headers"].(map[string]interface{})
headers, _ := xhttp["headers"].(map[string]any)
params["host"] = searchHost(headers)
}
params["mode"] = xhttp["mode"].(string)
}
security, _ := stream["security"].(string)
if security == "tls" {
params["security"] = "tls"
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
alpns, _ := tlsSetting["alpn"].([]interface{})
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
alpns, _ := tlsSetting["alpn"].([]any)
var alpn []string
for _, a := range alpns {
alpn = append(alpn, a.(string))
@@ -905,12 +831,12 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
}
externalProxies, _ := stream["externalProxy"].([]interface{})
externalProxies, _ := stream["externalProxy"].([]any)
if len(externalProxies) > 0 {
links := ""
for index, externalProxy := range externalProxies {
ep, _ := externalProxy.(map[string]interface{})
ep, _ := externalProxy.(map[string]any)
newSecurity, _ := ep["forceTls"].(string)
dest, _ := ep["dest"].(string)
port := int(ep["port"].(float64))
@@ -1007,18 +933,45 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
now := time.Now().Unix()
switch exp := stats.ExpiryTime / 1000; {
case exp > 0:
remark = append(remark, fmt.Sprintf("%d%s⏳", (exp-now)/86400, "Days"))
remainingSeconds := exp - now
days := remainingSeconds / 86400
hours := (remainingSeconds % 86400) / 3600
minutes := (remainingSeconds % 3600) / 60
if days > 0 {
if hours > 0 {
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
} else {
remark = append(remark, fmt.Sprintf("%dD⏳", days))
}
} else if hours > 0 {
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
} else {
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
}
case exp < 0:
remark = append(remark, fmt.Sprintf("%d%s⏳", exp/-86400, "Days"))
days := exp / -86400
hours := (exp % -86400) / 3600
minutes := (exp % -3600) / 60
if days > 0 {
if hours > 0 {
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
} else {
remark = append(remark, fmt.Sprintf("%dD⏳", days))
}
} else if hours > 0 {
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
} else {
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
}
}
}
}
return strings.Join(remark, separationChar)
}
func searchKey(data interface{}, key string) (interface{}, bool) {
func searchKey(data any, key string) (any, bool) {
switch val := data.(type) {
case map[string]interface{}:
case map[string]any:
for k, v := range val {
if k == key {
return v, true
@@ -1027,7 +980,7 @@ func searchKey(data interface{}, key string) (interface{}, bool) {
return result, true
}
}
case []interface{}:
case []any:
for _, v := range val {
if result, ok := searchKey(v, key); ok {
return result, true
@@ -1037,19 +990,19 @@ func searchKey(data interface{}, key string) (interface{}, bool) {
return nil, false
}
func searchHost(headers interface{}) string {
data, _ := headers.(map[string]interface{})
func searchHost(headers any) string {
data, _ := headers.(map[string]any)
for k, v := range data {
if strings.EqualFold(k, "host") {
switch v.(type) {
case []interface{}:
hosts, _ := v.([]interface{})
case []any:
hosts, _ := v.([]any)
if len(hosts) > 0 {
return hosts[0].(string)
} else {
return ""
}
case interface{}:
case any:
return v.(string)
}
}
@@ -1057,3 +1010,189 @@ func searchHost(headers interface{}) string {
return ""
}
// PageData is a view model for subpage.html
// PageData contains data for rendering the subscription information page.
type PageData struct {
Host string
BasePath string
SId string
Download string
Upload string
Total string
Used string
Remained string
Expire int64
LastOnline int64
Datepicker string
DownloadByte int64
UploadByte int64
TotalByte int64
SubUrl string
SubJsonUrl string
Result []string
}
// ResolveRequest extracts scheme and host info from request/headers consistently.
// ResolveRequest extracts scheme, host, and header information from an HTTP request.
func (s *SubService) ResolveRequest(c *gin.Context) (scheme string, host string, hostWithPort string, hostHeader string) {
// scheme
scheme = "http"
if c.Request.TLS != nil || strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https") {
scheme = "https"
}
// base host (no port)
if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil && h != "" {
host = h
}
if host == "" {
host = c.GetHeader("X-Real-IP")
}
if host == "" {
var err error
host, _, err = net.SplitHostPort(c.Request.Host)
if err != nil {
host = c.Request.Host
}
}
// host:port for URLs
hostWithPort = c.GetHeader("X-Forwarded-Host")
if hostWithPort == "" {
hostWithPort = c.Request.Host
}
if hostWithPort == "" {
hostWithPort = host
}
// header display host
hostHeader = c.GetHeader("X-Forwarded-Host")
if hostHeader == "" {
hostHeader = c.GetHeader("X-Real-IP")
}
if hostHeader == "" {
hostHeader = host
}
return
}
// BuildURLs constructs absolute subscription and JSON subscription URLs for a given subscription ID.
// It prioritizes configured URIs, then individual settings, and finally falls back to request-derived components.
func (s *SubService) BuildURLs(scheme, hostWithPort, subPath, subJsonPath, subId string) (subURL, subJsonURL string) {
// Input validation
if subId == "" {
return "", ""
}
// Get configured URIs first (highest priority)
configuredSubURI, _ := s.settingService.GetSubURI()
configuredSubJsonURI, _ := s.settingService.GetSubJsonURI()
// Determine base scheme and host (cached to avoid duplicate calls)
var baseScheme, baseHostWithPort string
if configuredSubURI == "" || configuredSubJsonURI == "" {
baseScheme, baseHostWithPort = s.getBaseSchemeAndHost(scheme, hostWithPort)
}
// Build subscription URL
subURL = s.buildSingleURL(configuredSubURI, baseScheme, baseHostWithPort, subPath, subId)
// Build JSON subscription URL
subJsonURL = s.buildSingleURL(configuredSubJsonURI, baseScheme, baseHostWithPort, subJsonPath, subId)
return subURL, subJsonURL
}
// getBaseSchemeAndHost determines the base scheme and host from settings or falls back to request values
func (s *SubService) getBaseSchemeAndHost(requestScheme, requestHostWithPort string) (string, string) {
subDomain, err := s.settingService.GetSubDomain()
if err != nil || subDomain == "" {
return requestScheme, requestHostWithPort
}
// Get port and TLS settings
subPort, _ := s.settingService.GetSubPort()
subKeyFile, _ := s.settingService.GetSubKeyFile()
subCertFile, _ := s.settingService.GetSubCertFile()
// Determine scheme from TLS configuration
scheme := "http"
if subKeyFile != "" && subCertFile != "" {
scheme = "https"
}
// Build host:port, always include port for clarity
hostWithPort := fmt.Sprintf("%s:%d", subDomain, subPort)
return scheme, hostWithPort
}
// buildSingleURL constructs a single URL using configured URI or base components
func (s *SubService) buildSingleURL(configuredURI, baseScheme, baseHostWithPort, basePath, subId string) string {
if configuredURI != "" {
return s.joinPathWithID(configuredURI, subId)
}
baseURL := fmt.Sprintf("%s://%s", baseScheme, baseHostWithPort)
return s.joinPathWithID(baseURL+basePath, subId)
}
// joinPathWithID safely joins a base path with a subscription ID
func (s *SubService) joinPathWithID(basePath, subId string) string {
if strings.HasSuffix(basePath, "/") {
return basePath + subId
}
return basePath + "/" + subId
}
// BuildPageData parses header and prepares the template view model.
// BuildPageData constructs page data for rendering the subscription information page.
func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray.ClientTraffic, lastOnline int64, subs []string, subURL, subJsonURL string, basePath string) PageData {
download := common.FormatTraffic(traffic.Down)
upload := common.FormatTraffic(traffic.Up)
total := "∞"
used := common.FormatTraffic(traffic.Up + traffic.Down)
remained := ""
if traffic.Total > 0 {
total = common.FormatTraffic(traffic.Total)
left := max(traffic.Total-(traffic.Up+traffic.Down), 0)
remained = common.FormatTraffic(left)
}
datepicker := s.datepicker
if datepicker == "" {
datepicker = "gregorian"
}
return PageData{
Host: hostHeader,
BasePath: basePath,
SId: subId,
Download: download,
Upload: upload,
Total: total,
Used: used,
Remained: remained,
Expire: traffic.ExpiryTime / 1000,
LastOnline: lastOnline,
Datepicker: datepicker,
DownloadByte: traffic.Down,
UploadByte: traffic.Up,
TotalByte: traffic.Total,
SubUrl: subURL,
SubJsonUrl: subJsonURL,
Result: subs,
}
}
func getHostFromXFH(s string) (string, error) {
if strings.Contains(s, ":") {
realHost, _, err := net.SplitHostPort(s)
if err != nil {
return "", err
}
return realHost, nil
}
return s, nil
}

258
update.sh Executable file
View File

@@ -0,0 +1,258 @@
#!/bin/bash
red='\033[0;31m'
green='\033[0;32m'
blue='\033[0;34m'
yellow='\033[0;33m'
plain='\033[0m'
# Don't edit this config
b_source="${BASH_SOURCE[0]}"
while [ -h "$b_source" ]; do
b_dir="$(cd -P "$(dirname "$b_source")" >/dev/null 2>&1 && pwd || pwd -P)"
b_source="$(readlink "$b_source")"
[[ $b_source != /* ]] && b_source="$b_dir/$b_source"
done
cur_dir="$(cd -P "$(dirname "$b_source")" >/dev/null 2>&1 && pwd || pwd -P)"
script_name=$(basename "$0")
# Check command exist function
_command_exists() {
type "$1" &>/dev/null
}
# Fail, log and exit script function
_fail() {
local msg=${1}
echo -e "${red}${msg}${plain}"
exit 2
}
# check root
[[ $EUID -ne 0 ]] && _fail "FATAL ERROR: Please run this script with root privilege."
if _command_exists wget; then
wget_bin=$(which wget)
else
_fail "ERROR: Command 'wget' not found."
fi
if _command_exists curl; then
curl_bin=$(which curl)
else
_fail "ERROR: Command 'curl' not found."
fi
# Check OS and set release variable
if [[ -f /etc/os-release ]]; then
source /etc/os-release
release=$ID
elif [[ -f /usr/lib/os-release ]]; then
source /usr/lib/os-release
release=$ID
else
_fail "Failed to check the system OS, please contact the author!"
fi
echo "The OS release is: $release"
arch() {
case "$(uname -m)" in
x86_64 | x64 | amd64) echo 'amd64' ;;
i*86 | x86) echo '386' ;;
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
armv7* | armv7 | arm) echo 'armv7' ;;
armv6* | armv6) echo 'armv6' ;;
armv5* | armv5) echo 'armv5' ;;
s390x) echo 's390x' ;;
*) echo -e "${red}Unsupported CPU architecture!${plain}" && rm -f "${cur_dir}/${script_name}" >/dev/null 2>&1 && exit 2;;
esac
}
echo "Arch: $(arch)"
install_base() {
echo -e "${green}Updating and install dependency packages...${plain}"
case "${release}" in
ubuntu | debian | armbian)
apt-get update >/dev/null 2>&1 && apt-get install -y -q wget curl tar tzdata >/dev/null 2>&1
;;
centos | rhel | almalinux | rocky | ol)
yum -y update >/dev/null 2>&1 && yum install -y -q wget curl tar tzdata >/dev/null 2>&1
;;
fedora | amzn | virtuozzo)
dnf -y update >/dev/null 2>&1 && dnf install -y -q wget curl tar tzdata >/dev/null 2>&1
;;
arch | manjaro | parch)
pacman -Syu >/dev/null 2>&1 && pacman -Syu --noconfirm wget curl tar tzdata >/dev/null 2>&1
;;
opensuse-tumbleweed | opensuse-leap)
zypper refresh >/dev/null 2>&1 && zypper -q install -y wget curl tar timezone >/dev/null 2>&1
;;
alpine)
apk update >/dev/null 2>&1 && apk add wget curl tar tzdata >/dev/null 2>&1
;;
*)
apt-get update >/dev/null 2>&1 && apt install -y -q wget curl tar tzdata >/dev/null 2>&1
;;
esac
}
config_after_update() {
echo -e "${yellow}x-ui settings:${plain}"
/usr/local/x-ui/x-ui setting -show true
/usr/local/x-ui/x-ui migrate
}
update_x-ui() {
cd /usr/local/
if [ -f "/usr/local/x-ui/x-ui" ]; then
current_xui_version=$(/usr/local/x-ui/x-ui -v)
echo -e "${green}Current x-ui version: ${current_xui_version}${plain}"
else
_fail "ERROR: Current x-ui version: unknown"
fi
echo -e "${green}Downloading new x-ui version...${plain}"
tag_version=$(${curl_bin} -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" 2>/dev/null | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [[ ! -n "$tag_version" ]]; then
echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
tag_version=$(${curl_bin} -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [[ ! -n "$tag_version" ]]; then
_fail "ERROR: Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later"
fi
fi
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
${wget_bin} -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null
if [[ $? -ne 0 ]]; then
echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
${wget_bin} --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null
if [[ $? -ne 0 ]]; then
_fail "ERROR: Failed to download x-ui, please be sure that your server can access GitHub"
fi
fi
if [[ -e /usr/local/x-ui/ ]]; then
echo -e "${green}Stopping x-ui...${plain}"
if [[ $release == "alpine" ]]; then
if [ -f "/etc/init.d/x-ui" ]; then
rc-service x-ui stop >/dev/null 2>&1
rc-update del x-ui >/dev/null 2>&1
echo -e "${green}Removing old service unit version...${plain}"
rm -f /etc/init.d/x-ui >/dev/null 2>&1
else
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
_fail "ERROR: x-ui service unit not installed."
fi
else
if [ -f "/etc/systemd/system/x-ui.service" ]; then
systemctl stop x-ui >/dev/null 2>&1
systemctl disable x-ui >/dev/null 2>&1
echo -e "${green}Removing old systemd unit version...${plain}"
rm /etc/systemd/system/x-ui.service -f >/dev/null 2>&1
systemctl daemon-reload >/dev/null 2>&1
else
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
_fail "ERROR: x-ui systemd unit not installed."
fi
fi
echo -e "${green}Removing old x-ui version...${plain}"
rm /usr/bin/x-ui -f >/dev/null 2>&1
rm /usr/local/x-ui/x-ui.service -f >/dev/null 2>&1
rm /usr/local/x-ui/x-ui -f >/dev/null 2>&1
rm /usr/local/x-ui/x-ui.sh -f >/dev/null 2>&1
echo -e "${green}Removing old xray version...${plain}"
rm /usr/local/x-ui/bin/xray-linux-amd64 -f >/dev/null 2>&1
echo -e "${green}Removing old README and LICENSE file...${plain}"
rm /usr/local/x-ui/bin/README.md -f >/dev/null 2>&1
rm /usr/local/x-ui/bin/LICENSE -f >/dev/null 2>&1
else
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
_fail "ERROR: x-ui not installed."
fi
echo -e "${green}Installing new x-ui version...${plain}"
tar zxvf x-ui-linux-$(arch).tar.gz >/dev/null 2>&1
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
cd x-ui >/dev/null 2>&1
chmod +x x-ui >/dev/null 2>&1
# Check the system's architecture and rename the file accordingly
if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then
mv bin/xray-linux-$(arch) bin/xray-linux-arm >/dev/null 2>&1
chmod +x bin/xray-linux-arm >/dev/null 2>&1
fi
chmod +x x-ui bin/xray-linux-$(arch) >/dev/null 2>&1
echo -e "${green}Downloading and installing x-ui.sh script...${plain}"
${wget_bin} -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh >/dev/null 2>&1
if [[ $? -ne 0 ]]; then
echo -e "${yellow}Trying to fetch x-ui with IPv4...${plain}"
${wget_bin} --inet4-only -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh >/dev/null 2>&1
if [[ $? -ne 0 ]]; then
_fail "ERROR: Failed to download x-ui.sh script, please be sure that your server can access GitHub"
fi
fi
chmod +x /usr/local/x-ui/x-ui.sh >/dev/null 2>&1
chmod +x /usr/bin/x-ui >/dev/null 2>&1
echo -e "${green}Changing owner...${plain}"
chown -R root:root /usr/local/x-ui >/dev/null 2>&1
if [ -f "/usr/local/x-ui/bin/config.json" ]; then
echo -e "${green}Changing on config file permissions...${plain}"
chmod 640 /usr/local/x-ui/bin/config.json >/dev/null 2>&1
fi
if [[ $release == "alpine" ]]; then
echo -e "${green}Downloading and installing startup unit x-ui.rc...${plain}"
${wget_bin} -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc >/dev/null 2>&1
if [[ $? -ne 0 ]]; then
${wget_bin} --inet4-only -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc >/dev/null 2>&1
if [[ $? -ne 0 ]]; then
_fail "ERROR: Failed to download startup unit x-ui.rc, please be sure that your server can access GitHub"
fi
fi
chmod +x /etc/init.d/x-ui >/dev/null 2>&1
chown root:root /etc/init.d/x-ui >/dev/null 2>&1
rc-update add x-ui >/dev/null 2>&1
rc-service x-ui start >/dev/null 2>&1
else
echo -e "${green}Installing systemd unit...${plain}"
cp -f x-ui.service /etc/systemd/system/ >/dev/null 2>&1
chown root:root /etc/systemd/system/x-ui.service >/dev/null 2>&1
systemctl daemon-reload >/dev/null 2>&1
systemctl enable x-ui >/dev/null 2>&1
systemctl start x-ui >/dev/null 2>&1
fi
config_after_update
echo -e "${green}x-ui ${tag_version}${plain} updating finished, it is running now..."
echo -e ""
echo -e "┌───────────────────────────────────────────────────────┐
${blue}x-ui control menu usages (subcommands):${plain}
│ │
${blue}x-ui${plain} - Admin Management Script │
${blue}x-ui start${plain} - Start │
${blue}x-ui stop${plain} - Stop │
${blue}x-ui restart${plain} - Restart │
${blue}x-ui status${plain} - Current Status │
${blue}x-ui settings${plain} - Current Settings │
${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
${blue}x-ui log${plain} - Check logs │
${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
${blue}x-ui update${plain} - Update │
${blue}x-ui legacy${plain} - legacy version │
${blue}x-ui install${plain} - Install │
${blue}x-ui uninstall${plain} - Uninstall │
└───────────────────────────────────────────────────────┘"
}
echo -e "${green}Running...${plain}"
install_base
update_x-ui $1

View File

@@ -1,23 +1,27 @@
// Package common provides common utility functions for error handling, formatting, and multi-error management.
package common
import (
"errors"
"fmt"
"x-ui/logger"
"github.com/mhsanaei/3x-ui/v2/logger"
)
func NewErrorf(format string, a ...interface{}) error {
// NewErrorf creates a new error with formatted message.
func NewErrorf(format string, a ...any) error {
msg := fmt.Sprintf(format, a...)
return errors.New(msg)
}
func NewError(a ...interface{}) error {
// NewError creates a new error from the given arguments.
func NewError(a ...any) error {
msg := fmt.Sprintln(a...)
return errors.New(msg)
}
func Recover(msg string) interface{} {
// Recover handles panic recovery and logs the panic error if a message is provided.
func Recover(msg string) any {
panicErr := recover()
if panicErr != nil {
if msg != "" {

View File

@@ -4,18 +4,15 @@ import (
"fmt"
)
func FormatTraffic(trafficBytes int64) (size string) {
if trafficBytes < 1024 {
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
} else if trafficBytes < (1024 * 1024) {
return fmt.Sprintf("%.2fKB", float64(trafficBytes)/float64(1024))
} else if trafficBytes < (1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fMB", float64(trafficBytes)/float64(1024*1024))
} else if trafficBytes < (1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fGB", float64(trafficBytes)/float64(1024*1024*1024))
} else if trafficBytes < (1024 * 1024 * 1024 * 1024 * 1024) {
return fmt.Sprintf("%.2fTB", float64(trafficBytes)/float64(1024*1024*1024*1024))
} else {
return fmt.Sprintf("%.2fEB", float64(trafficBytes)/float64(1024*1024*1024*1024*1024))
// FormatTraffic formats traffic bytes into human-readable units (B, KB, MB, GB, TB, PB).
func FormatTraffic(trafficBytes int64) string {
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
unitIndex := 0
size := float64(trafficBytes)
for size >= 1024 && unitIndex < len(units)-1 {
size /= 1024
unitIndex++
}
return fmt.Sprintf("%.2f%s", size, units[unitIndex])
}

View File

@@ -4,8 +4,10 @@ import (
"strings"
)
// multiError represents a collection of errors.
type multiError []error
// Error returns a string representation of all errors joined with " | ".
func (e multiError) Error() string {
var r strings.Builder
r.WriteString("multierr: ")
@@ -16,6 +18,7 @@ func (e multiError) Error() string {
return r.String()
}
// Combine combines multiple errors into a single error, filtering out nil errors.
func Combine(maybeError ...error) error {
var errs multiError
for _, err := range maybeError {

18
util/crypto/crypto.go Normal file
View File

@@ -0,0 +1,18 @@
// Package crypto provides cryptographic utilities for password hashing and verification.
package crypto
import (
"golang.org/x/crypto/bcrypt"
)
// HashPasswordAsBcrypt generates a bcrypt hash of the given password.
func HashPasswordAsBcrypt(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(hash), err
}
// CheckPasswordHash verifies if the given password matches the bcrypt hash.
func CheckPasswordHash(hash, password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

View File

@@ -1,12 +1,15 @@
// Package json_util provides JSON utilities including a custom RawMessage type.
package json_util
import (
"errors"
)
// RawMessage is a custom JSON raw message type that marshals empty slices as "null".
type RawMessage []byte
// MarshalJSON: Customize json.RawMessage default behavior
// MarshalJSON customizes the JSON marshaling behavior for RawMessage.
// Empty RawMessage values are marshaled as "null" instead of "[]".
func (m RawMessage) MarshalJSON() ([]byte, error) {
if len(m) == 0 {
return []byte("null"), nil
@@ -14,7 +17,7 @@ func (m RawMessage) MarshalJSON() ([]byte, error) {
return m, nil
}
// UnmarshalJSON: sets *m to a copy of data.
// UnmarshalJSON sets *m to a copy of the JSON data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")

144
util/ldap/ldap.go Normal file
View File

@@ -0,0 +1,144 @@
package ldaputil
import (
"crypto/tls"
"fmt"
"github.com/go-ldap/ldap/v3"
)
type Config struct {
Host string
Port int
UseTLS bool
BindDN string
Password string
BaseDN string
UserFilter string
UserAttr string
FlagField string
TruthyVals []string
Invert bool
}
// FetchVlessFlags returns map[email]enabled
func FetchVlessFlags(cfg Config) (map[string]bool, error) {
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
var conn *ldap.Conn
var err error
if cfg.UseTLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return nil, err
}
defer conn.Close()
if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
return nil, err
}
}
if cfg.UserFilter == "" {
cfg.UserFilter = "(objectClass=person)"
}
if cfg.UserAttr == "" {
cfg.UserAttr = "mail"
}
// if field not set we fallback to legacy vless_enabled
if cfg.FlagField == "" {
cfg.FlagField = "vless_enabled"
}
req := ldap.NewSearchRequest(
cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
cfg.UserFilter,
[]string{cfg.UserAttr, cfg.FlagField},
nil,
)
res, err := conn.Search(req)
if err != nil {
return nil, err
}
result := make(map[string]bool, len(res.Entries))
for _, e := range res.Entries {
user := e.GetAttributeValue(cfg.UserAttr)
if user == "" {
continue
}
val := e.GetAttributeValue(cfg.FlagField)
enabled := false
for _, t := range cfg.TruthyVals {
if val == t {
enabled = true
break
}
}
if cfg.Invert {
enabled = !enabled
}
result[user] = enabled
}
return result, nil
}
// AuthenticateUser searches user by cfg.UserAttr and attempts to bind with provided password.
func AuthenticateUser(cfg Config, username, password string) (bool, error) {
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
var conn *ldap.Conn
var err error
if cfg.UseTLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return false, err
}
defer conn.Close()
// Optional initial bind for search
if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
return false, err
}
}
if cfg.UserFilter == "" {
cfg.UserFilter = "(objectClass=person)"
}
if cfg.UserAttr == "" {
cfg.UserAttr = "uid"
}
// Build filter to find specific user
filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username))
req := ldap.NewSearchRequest(
cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false,
filter,
[]string{"dn"},
nil,
)
res, err := conn.Search(req)
if err != nil {
return false, err
}
if len(res.Entries) == 0 {
return false, nil
}
userDN := res.Entries[0].DN
// Try to bind as the user
if err := conn.Bind(userDN, password); err != nil {
return false, nil
}
return true, nil
}

View File

@@ -1,7 +1,9 @@
// Package random provides utilities for generating random strings and numbers.
package random
import (
"math/rand"
"crypto/rand"
"math/big"
)
var (
@@ -13,6 +15,8 @@ var (
allSeq [62]rune
)
// init initializes the character sequences used for random string generation.
// It sets up arrays for numbers, lowercase letters, uppercase letters, and combinations.
func init() {
for i := 0; i < 10; i++ {
numSeq[i] = rune('0' + i)
@@ -33,14 +37,25 @@ func init() {
copy(allSeq[len(numSeq)+len(lowerSeq):], upperSeq[:])
}
// Seq generates a random string of length n containing alphanumeric characters (numbers, lowercase and uppercase letters).
func Seq(n int) string {
runes := make([]rune, n)
for i := 0; i < n; i++ {
runes[i] = allSeq[rand.Intn(len(allSeq))]
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(allSeq))))
if err != nil {
panic("crypto/rand failed: " + err.Error())
}
runes[i] = allSeq[idx.Int64()]
}
return string(runes)
}
// Num generates a random integer between 0 and n-1.
func Num(n int) int {
return rand.Intn(n)
bn := big.NewInt(int64(n))
r, err := rand.Int(rand.Reader, bn)
if err != nil {
panic("crypto/rand failed: " + err.Error())
}
return int(r.Int64())
}

View File

@@ -1,7 +1,9 @@
// Package reflect_util provides reflection utilities for working with struct fields and values.
package reflect_util
import "reflect"
// GetFields returns all struct fields of the given reflect.Type.
func GetFields(t reflect.Type) []reflect.StructField {
num := t.NumField()
fields := make([]reflect.StructField, 0, num)
@@ -11,6 +13,7 @@ func GetFields(t reflect.Type) []reflect.StructField {
return fields
}
// GetFieldValues returns all field values of the given reflect.Value.
func GetFieldValues(v reflect.Value) []reflect.Value {
num := v.NumField()
fields := make([]reflect.Value, 0, num)

View File

@@ -1,3 +1,5 @@
// Package sys provides system utilities for monitoring network connections and CPU usage.
// Platform-specific implementations are provided for Windows, Linux, and macOS.
package sys
import (

View File

@@ -4,7 +4,12 @@
package sys
import (
"encoding/binary"
"fmt"
"sync"
"github.com/shirou/gopsutil/v4/net"
"golang.org/x/sys/unix"
)
func GetTCPCount() (int, error) {
@@ -22,3 +27,69 @@ func GetUDPCount() (int, error) {
}
return len(stats), nil
}
// --- CPU Utilization (macOS native) ---
// sysctl kern.cp_time returns an array of 5 longs: user, nice, sys, idle, intr.
// We compute utilization deltas without cgo.
var (
cpuMu sync.Mutex
lastTotals [5]uint64
hasLastCPUT bool
)
func CPUPercentRaw() (float64, error) {
raw, err := unix.SysctlRaw("kern.cp_time")
if err != nil {
return 0, err
}
// Expect either 5*8 bytes (uint64) or 5*4 bytes (uint32)
var out [5]uint64
switch len(raw) {
case 5 * 8:
for i := 0; i < 5; i++ {
out[i] = binary.LittleEndian.Uint64(raw[i*8 : (i+1)*8])
}
case 5 * 4:
for i := 0; i < 5; i++ {
out[i] = uint64(binary.LittleEndian.Uint32(raw[i*4 : (i+1)*4]))
}
default:
return 0, fmt.Errorf("unexpected kern.cp_time size: %d", len(raw))
}
// user, nice, sys, idle, intr
user := out[0]
nice := out[1]
sysv := out[2]
idle := out[3]
intr := out[4]
cpuMu.Lock()
defer cpuMu.Unlock()
if !hasLastCPUT {
lastTotals = out
hasLastCPUT = true
return 0, nil
}
dUser := user - lastTotals[0]
dNice := nice - lastTotals[1]
dSys := sysv - lastTotals[2]
dIdle := idle - lastTotals[3]
dIntr := intr - lastTotals[4]
lastTotals = out
totald := dUser + dNice + dSys + dIdle + dIntr
if totald == 0 {
return 0, nil
}
busy := totald - dIdle
pct := float64(busy) / float64(totald) * 100.0
if pct > 100 {
pct = 100
}
return pct, nil
}

View File

@@ -4,10 +4,14 @@
package sys
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strconv"
"strings"
"sync"
)
func getLinesNum(filename string) (int, error) {
@@ -41,14 +45,16 @@ func getLinesNum(filename string) (int, error) {
return sum, nil
}
// GetTCPCount returns the number of active TCP connections by reading
// /proc/net/tcp and /proc/net/tcp6 when available.
func GetTCPCount() (int, error) {
root := HostProc()
tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root))
tcp4, err := safeGetLinesNum(fmt.Sprintf("%v/net/tcp", root))
if err != nil {
return 0, err
}
tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root))
tcp6, err := safeGetLinesNum(fmt.Sprintf("%v/net/tcp6", root))
if err != nil {
return 0, err
}
@@ -59,14 +65,121 @@ func GetTCPCount() (int, error) {
func GetUDPCount() (int, error) {
root := HostProc()
udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root))
udp4, err := safeGetLinesNum(fmt.Sprintf("%v/net/udp", root))
if err != nil {
return 0, err
}
udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root))
udp6, err := safeGetLinesNum(fmt.Sprintf("%v/net/udp6", root))
if err != nil {
return 0, err
}
return udp4 + udp6, nil
}
// safeGetLinesNum returns 0 if the file does not exist, otherwise forwards
// to getLinesNum to count the number of lines.
func safeGetLinesNum(path string) (int, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return 0, nil
} else if err != nil {
return 0, err
}
return getLinesNum(path)
}
// --- CPU Utilization (Linux native) ---
var (
cpuMu sync.Mutex
lastTotal uint64
lastIdleAll uint64
hasLast bool
)
// CPUPercentRaw returns instantaneous total CPU utilization by reading /proc/stat.
// First call initializes and returns 0; subsequent calls return busy/total * 100.
func CPUPercentRaw() (float64, error) {
f, err := os.Open("/proc/stat")
if err != nil {
return 0, err
}
defer f.Close()
rd := bufio.NewReader(f)
line, err := rd.ReadString('\n')
if err != nil && err != io.EOF {
return 0, err
}
// Expect line like: cpu user nice system idle iowait irq softirq steal guest guest_nice
fields := strings.Fields(line)
if len(fields) < 5 || fields[0] != "cpu" {
return 0, fmt.Errorf("unexpected /proc/stat format")
}
var nums []uint64
for i := 1; i < len(fields); i++ {
v, err := strconv.ParseUint(fields[i], 10, 64)
if err != nil {
break
}
nums = append(nums, v)
}
if len(nums) < 4 { // need at least user,nice,system,idle
return 0, fmt.Errorf("insufficient cpu fields")
}
// Conform with standard Linux CPU accounting
var user, nice, system, idle, iowait, irq, softirq, steal uint64
user = nums[0]
if len(nums) > 1 {
nice = nums[1]
}
if len(nums) > 2 {
system = nums[2]
}
if len(nums) > 3 {
idle = nums[3]
}
if len(nums) > 4 {
iowait = nums[4]
}
if len(nums) > 5 {
irq = nums[5]
}
if len(nums) > 6 {
softirq = nums[6]
}
if len(nums) > 7 {
steal = nums[7]
}
idleAll := idle + iowait
nonIdle := user + nice + system + irq + softirq + steal
total := idleAll + nonIdle
cpuMu.Lock()
defer cpuMu.Unlock()
if !hasLast {
lastTotal = total
lastIdleAll = idleAll
hasLast = true
return 0, nil
}
totald := total - lastTotal
idled := idleAll - lastIdleAll
lastTotal = total
lastIdleAll = idleAll
if totald == 0 {
return 0, nil
}
busy := totald - idled
pct := float64(busy) / float64(totald) * 100.0
if pct > 100 {
pct = 100
}
return pct, nil
}

View File

@@ -5,10 +5,14 @@ package sys
import (
"errors"
"sync"
"syscall"
"unsafe"
"github.com/shirou/gopsutil/v4/net"
)
// GetConnectionCount returns the number of active connections for the specified protocol ("tcp" or "udp").
func GetConnectionCount(proto string) (int, error) {
if proto != "tcp" && proto != "udp" {
return 0, errors.New("invalid protocol")
@@ -21,10 +25,92 @@ func GetConnectionCount(proto string) (int, error) {
return len(stats), nil
}
// GetTCPCount returns the number of active TCP connections.
func GetTCPCount() (int, error) {
return GetConnectionCount("tcp")
}
// GetUDPCount returns the number of active UDP connections.
func GetUDPCount() (int, error) {
return GetConnectionCount("udp")
}
// --- CPU Utilization (Windows native) ---
var (
modKernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetSystemTimes = modKernel32.NewProc("GetSystemTimes")
cpuMu sync.Mutex
lastIdle uint64
lastKernel uint64
lastUser uint64
hasLast bool
)
type filetime struct {
LowDateTime uint32
HighDateTime uint32
}
// ftToUint64 converts a Windows FILETIME-like struct to a uint64 for
// arithmetic and delta calculations used by CPUPercentRaw.
func ftToUint64(ft filetime) uint64 {
return (uint64(ft.HighDateTime) << 32) | uint64(ft.LowDateTime)
}
// CPUPercentRaw returns the instantaneous total CPU utilization percentage using
// Windows GetSystemTimes across all logical processors. The first call returns 0
// as it initializes the baseline. Subsequent calls compute deltas.
func CPUPercentRaw() (float64, error) {
var idleFT, kernelFT, userFT filetime
r1, _, e1 := procGetSystemTimes.Call(
uintptr(unsafe.Pointer(&idleFT)),
uintptr(unsafe.Pointer(&kernelFT)),
uintptr(unsafe.Pointer(&userFT)),
)
if r1 == 0 { // failure
if e1 != nil {
return 0, e1
}
return 0, syscall.GetLastError()
}
idle := ftToUint64(idleFT)
kernel := ftToUint64(kernelFT)
user := ftToUint64(userFT)
cpuMu.Lock()
defer cpuMu.Unlock()
if !hasLast {
lastIdle = idle
lastKernel = kernel
lastUser = user
hasLast = true
return 0, nil
}
idleDelta := idle - lastIdle
kernelDelta := kernel - lastKernel
userDelta := user - lastUser
// Update for next call
lastIdle = idle
lastKernel = kernel
lastUser = user
total := kernelDelta + userDelta
if total == 0 {
return 0, nil
}
// On Windows, kernel time includes idle time; busy = total - idle
busy := total - idleDelta
pct := float64(busy) / float64(total) * 100.0
// lower bound not needed; ratios of uint64 are non-negative
if pct > 100 {
pct = 100
}
return pct, nil
}

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +0,0 @@
@import "../lib/style/index.less";
@import "../lib/style/components.less";
@green-6: #008771;
@primary-color: @green-6;
@border-radius-base: 1rem;
@progress-remaining-color: #EDEDED;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory(global):typeof define==="function"&&define.amd?define(factory):factory(global)})(typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:this,function(global){"use strict";var _Base64=global.Base64;var version="2.5.0";var buffer;if(typeof module!=="undefined"&&module.exports){try{buffer=eval("require('buffer').Buffer")}catch(err){buffer=undefined}}var b64chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var b64tab=function(bin){var t={};for(var i=0,l=bin.length;i<l;i++)t[bin.charAt(i)]=i;return t}(b64chars);var fromCharCode=String.fromCharCode;var cb_utob=function(c){if(c.length<2){var cc=c.charCodeAt(0);return cc<128?c:cc<2048?fromCharCode(192|cc>>>6)+fromCharCode(128|cc&63):fromCharCode(224|cc>>>12&15)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}else{var cc=65536+(c.charCodeAt(0)-55296)*1024+(c.charCodeAt(1)-56320);return fromCharCode(240|cc>>>18&7)+fromCharCode(128|cc>>>12&63)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}};var re_utob=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;var utob=function(u){return u.replace(re_utob,cb_utob)};var cb_encode=function(ccc){var padlen=[0,2,1][ccc.length%3],ord=ccc.charCodeAt(0)<<16|(ccc.length>1?ccc.charCodeAt(1):0)<<8|(ccc.length>2?ccc.charCodeAt(2):0),chars=[b64chars.charAt(ord>>>18),b64chars.charAt(ord>>>12&63),padlen>=2?"=":b64chars.charAt(ord>>>6&63),padlen>=1?"=":b64chars.charAt(ord&63)];return chars.join("")};var btoa=global.btoa?function(b){return global.btoa(b)}:function(b){return b.replace(/[\s\S]{1,3}/g,cb_encode)};var _encode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(u){return(u.constructor===buffer.constructor?u:buffer.from(u)).toString("base64")}:function(u){return(u.constructor===buffer.constructor?u:new buffer(u)).toString("base64")}:function(u){return btoa(utob(u))};var encode=function(u,urisafe){return!urisafe?_encode(String(u)):_encode(String(u)).replace(/[+\/]/g,function(m0){return m0=="+"?"-":"_"}).replace(/=/g,"")};var encodeURI=function(u){return encode(u,true)};var re_btou=new RegExp(["[À-ß][€-¿]","[à-ï][€-¿]{2}","[ð-÷][€-¿]{3}"].join("|"),"g");var cb_btou=function(cccc){switch(cccc.length){case 4:var cp=(7&cccc.charCodeAt(0))<<18|(63&cccc.charCodeAt(1))<<12|(63&cccc.charCodeAt(2))<<6|63&cccc.charCodeAt(3),offset=cp-65536;return fromCharCode((offset>>>10)+55296)+fromCharCode((offset&1023)+56320);case 3:return fromCharCode((15&cccc.charCodeAt(0))<<12|(63&cccc.charCodeAt(1))<<6|63&cccc.charCodeAt(2));default:return fromCharCode((31&cccc.charCodeAt(0))<<6|63&cccc.charCodeAt(1))}};var btou=function(b){return b.replace(re_btou,cb_btou)};var cb_decode=function(cccc){var len=cccc.length,padlen=len%4,n=(len>0?b64tab[cccc.charAt(0)]<<18:0)|(len>1?b64tab[cccc.charAt(1)]<<12:0)|(len>2?b64tab[cccc.charAt(2)]<<6:0)|(len>3?b64tab[cccc.charAt(3)]:0),chars=[fromCharCode(n>>>16),fromCharCode(n>>>8&255),fromCharCode(n&255)];chars.length-=[0,0,2,1][padlen];return chars.join("")};var _atob=global.atob?function(a){return global.atob(a)}:function(a){return a.replace(/\S{1,4}/g,cb_decode)};var atob=function(a){return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g,""))};var _decode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(a){return(a.constructor===buffer.constructor?a:buffer.from(a,"base64")).toString()}:function(a){return(a.constructor===buffer.constructor?a:new buffer(a,"base64")).toString()}:function(a){return btou(_atob(a))};var decode=function(a){return _decode(String(a).replace(/[-_]/g,function(m0){return m0=="-"?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,""))};var noConflict=function(){var Base64=global.Base64;global.Base64=_Base64;return Base64};global.Base64={VERSION:version,atob:atob,btoa:btoa,fromBase64:decode,toBase64:encode,utob:utob,encode:encode,encodeURI:encodeURI,btou:btou,decode:decode,noConflict:noConflict,__buffer__:buffer};if(typeof Object.defineProperty==="function"){var noEnum=function(v){return{value:v,enumerable:false,writable:true,configurable:true}};global.Base64.extendString=function(){Object.defineProperty(String.prototype,"fromBase64",noEnum(function(){return decode(this)}));Object.defineProperty(String.prototype,"toBase64",noEnum(function(urisafe){return encode(this,urisafe)}));Object.defineProperty(String.prototype,"toBase64URI",noEnum(function(){return encode(this,true)}))}}if(global["Meteor"]){Base64=global.Base64}if(typeof module!=="undefined"&&module.exports){module.exports.Base64=global.Base64}else if(typeof define==="function"&&define.amd){define([],function(){return global.Base64})}return{Base64:global.Base64}});

File diff suppressed because one or more lines are too long

View File

@@ -63,7 +63,7 @@
return scriptHint(editor, javascriptKeywords,
function (e, cur) {return e.getTokenAt(cur);},
options);
};
}
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
function getCoffeeScriptToken(editor, cur) {

View File

@@ -362,7 +362,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == wanted) return cont();
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp);
};
}
return exp;
}

View File

@@ -6,7 +6,7 @@
.CodeMirror-lint-tooltip {
background-color: #ffd;
border: 1px solid black;
border-radius: 4px 4px 4px 4px;
border-radius: 4px;
color: black;
font-family: monospace;
font-size: 10pt;

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
@media only screen and (max-width:767px){.hidden-xs-only{display:none!important}}@media only screen and (min-width:768px){.hidden-sm-and-up{display:none!important}}@media only screen and (min-width:768px) and (max-width:991px){.hidden-sm-only{display:none!important}}@media only screen and (max-width:991px){.hidden-sm-and-down{display:none!important}}@media only screen and (min-width:992px){.hidden-md-and-up{display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.hidden-md-only{display:none!important}}@media only screen and (max-width:1199px){.hidden-md-and-down{display:none!important}}@media only screen and (min-width:1200px){.hidden-lg-and-up{display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.hidden-lg-only{display:none!important}}@media only screen and (max-width:1919px){.hidden-lg-and-down{display:none!important}}@media only screen and (min-width:1920px){.hidden-xl-only{display:none!important}}

View File

@@ -1,83 +0,0 @@
const supportLangs = [
{
name: 'English',
value: 'en-US',
icon: '🇺🇸',
},
{
name: 'فارسی',
value: 'fa-IR',
icon: '🇮🇷',
},
{
name: '汉语',
value: 'zh-Hans',
icon: '🇨🇳',
},
{
name: 'Русский',
value: 'ru-RU',
icon: '🇷🇺',
},
{
name: 'Tiếng Việt',
value: 'vi-VN',
icon: '🇻🇳',
},
{
name: 'Español',
value: 'es-ES',
icon: '🇪🇸',
},
{
name: 'Indonesian',
value: 'id-ID',
icon: '🇮🇩',
},
{
name: 'Український',
value: 'uk-UA',
icon: '🇺🇦',
},
];
function getLang() {
let lang = getCookie('lang');
if (!lang) {
if (window.navigator) {
lang = window.navigator.language || window.navigator.userLanguage;
if (isSupportLang(lang)) {
setCookie('lang', lang, 150);
} else {
setCookie('lang', 'en-US', 150);
window.location.reload();
}
} else {
setCookie('lang', 'en-US', 150);
window.location.reload();
}
}
return lang;
}
function setLang(lang) {
if (!isSupportLang(lang)) {
lang = 'en-US';
}
setCookie('lang', lang, 150);
window.location.reload();
}
function isSupportLang(lang) {
for (l of supportLangs) {
if (l.value === lang) {
return true;
}
}
return false;
}

View File

@@ -6,9 +6,12 @@ class DBInbound {
this.up = 0;
this.down = 0;
this.total = 0;
this.allTime = 0;
this.remark = "";
this.enable = true;
this.expiryTime = 0;
this.trafficReset = "never";
this.lastTrafficResetTime = 0;
this.listen = "";
this.port = 0;
@@ -25,11 +28,11 @@ class DBInbound {
}
get totalGB() {
return toFixed(this.total / ONE_GB, 2);
return NumberFormatter.toFixed(this.total / SizeFormatter.ONE_GB, 2);
}
set totalGB(gb) {
this.total = toFixed(gb * ONE_GB, 0);
this.total = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
}
get isVMess() {
@@ -48,8 +51,8 @@ class DBInbound {
return this.protocol === Protocols.SHADOWSOCKS;
}
get isSocks() {
return this.protocol === Protocols.SOCKS;
get isMixed() {
return this.protocol === Protocols.MIXED;
}
get isHTTP() {
@@ -140,9 +143,9 @@ class DBInbound {
return false;
}
}
genInboundLinks(remarkModel) {
genInboundLinks(remarkModel) {
const inbound = this.toInbound();
return inbound.genInboundLinks(this.remark,remarkModel);
return inbound.genInboundLinks(this.remark, remarkModel);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,41 +7,70 @@ class AllSetting {
this.webCertFile = "";
this.webKeyFile = "";
this.webBasePath = "/";
this.sessionMaxAge = "";
this.pageSize = 0;
this.expireDiff = "";
this.trafficDiff = "";
this.sessionMaxAge = 360;
this.pageSize = 25;
this.expireDiff = 0;
this.trafficDiff = 0;
this.remarkModel = "-ieo";
this.datepicker = "gregorian";
this.tgBotEnable = false;
this.tgBotToken = "";
this.tgBotProxy = "";
this.tgBotAPIServer = "";
this.tgBotChatId = "";
this.tgRunTime = "@daily";
this.tgBotBackup = false;
this.tgBotLoginNotify = false;
this.tgCpu = "";
this.tgBotLoginNotify = true;
this.tgCpu = 80;
this.tgLang = "en-US";
this.twoFactorEnable = false;
this.twoFactorToken = "";
this.xrayTemplateConfig = "";
this.secretEnable = false;
this.subEnable = false;
this.subEnable = true;
this.subJsonEnable = false;
this.subTitle = "";
this.subListen = "";
this.subPort = "2096";
this.subPort = 2096;
this.subPath = "/sub/";
this.subJsonPath = "/json/";
this.subDomain = "";
this.externalTrafficInformEnable = false;
this.externalTrafficInformURI = "";
this.subCertFile = "";
this.subKeyFile = "";
this.subUpdates = 0;
this.subUpdates = 12;
this.subEncrypt = true;
this.subShowInfo = false;
this.subShowInfo = true;
this.subURI = "";
this.subJsonURI = "";
this.subJsonFragment = "";
this.subJsonNoises = "";
this.subJsonMux = "";
this.subJsonRules = "";
this.timeLocation = "Asia/Tehran";
this.timeLocation = "Local";
// LDAP settings
this.ldapEnable = false;
this.ldapHost = "";
this.ldapPort = 389;
this.ldapUseTLS = false;
this.ldapBindDN = "";
this.ldapPassword = "";
this.ldapBaseDN = "";
this.ldapUserFilter = "(objectClass=person)";
this.ldapUserAttr = "mail";
this.ldapVlessField = "vless_enabled";
this.ldapSyncCron = "@every 1m";
this.ldapFlagField = "";
this.ldapTruthyValues = "true,1,yes,on";
this.ldapInvertFlag = false;
this.ldapInboundTags = "";
this.ldapAutoCreate = false;
this.ldapAutoDelete = false;
this.ldapDefaultTotalGB = 0;
this.ldapDefaultExpiryDays = 0;
this.ldapDefaultLimitIP = 0;
if (data == null) {
return

View File

@@ -0,0 +1,160 @@
(function () {
// Vue app for Subscription page
const el = document.getElementById('subscription-data');
if (!el) return;
const textarea = document.getElementById('subscription-links');
const rawLinks = (textarea?.value || '').split('\n').filter(Boolean);
const data = {
sId: el.getAttribute('data-sid') || '',
subUrl: el.getAttribute('data-sub-url') || '',
subJsonUrl: el.getAttribute('data-subjson-url') || '',
download: el.getAttribute('data-download') || '',
upload: el.getAttribute('data-upload') || '',
used: el.getAttribute('data-used') || '',
total: el.getAttribute('data-total') || '',
remained: el.getAttribute('data-remained') || '',
expireMs: (parseInt(el.getAttribute('data-expire') || '0', 10) || 0) * 1000,
lastOnlineMs: (parseInt(el.getAttribute('data-lastonline') || '0', 10) || 0),
downloadByte: parseInt(el.getAttribute('data-downloadbyte') || '0', 10) || 0,
uploadByte: parseInt(el.getAttribute('data-uploadbyte') || '0', 10) || 0,
totalByte: parseInt(el.getAttribute('data-totalbyte') || '0', 10) || 0,
datepicker: el.getAttribute('data-datepicker') || 'gregorian',
};
// Normalize lastOnline to milliseconds if it looks like seconds
if (data.lastOnlineMs && data.lastOnlineMs < 10_000_000_000) {
data.lastOnlineMs *= 1000;
}
function renderLink(item) {
return (
Vue.h('a-list-item', {}, [
Vue.h('a-space', { props: { size: 'small' } }, [
Vue.h('a-button', { props: { size: 'small' }, on: { click: () => copy(item) } }, [Vue.h('a-icon', { props: { type: 'copy' } })]),
Vue.h('span', { class: 'break-all' }, item)
])
])
);
}
function copy(text) {
ClipboardManager.copyText(text).then(ok => {
const messageType = ok ? 'success' : 'error';
Vue.prototype.$message[messageType](ok ? 'Copied' : 'Copy failed');
});
}
function open(url) {
window.location.href = url;
}
function drawQR(value) {
try {
new QRious({ element: document.getElementById('qrcode'), value, size: 220 });
} catch (e) {
console.warn(e);
}
}
// Try to extract a human label (email/ps) from different link types
function linkName(link, idx) {
try {
if (link.startsWith('vmess://')) {
const json = JSON.parse(atob(link.replace('vmess://', '')));
if (json.ps) return json.ps;
if (json.add && json.id) return json.add; // fallback host
} else if (link.startsWith('vless://') || link.startsWith('trojan://')) {
const hashIdx = link.indexOf('#');
if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1));
const qIdx = link.indexOf('?');
if (qIdx !== -1) {
const qs = new URL('http://x/?' + link.substring(qIdx + 1, hashIdx !== -1 ? hashIdx : undefined)).searchParams;
if (qs.get('remark')) return qs.get('remark');
if (qs.get('email')) return qs.get('email');
}
const at = link.indexOf('@');
const protSep = link.indexOf('://');
if (at !== -1 && protSep !== -1) return link.substring(protSep + 3, at);
} else if (link.startsWith('ss://')) {
const hashIdx = link.indexOf('#');
if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1));
}
} catch (e) { /* ignore and fallback */ }
return 'Link ' + (idx + 1);
}
const app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
data: {
themeSwitcher,
app: data,
links: rawLinks,
lang: '',
viewportWidth: (typeof window !== 'undefined' ? window.innerWidth : 1024),
},
async mounted() {
this.lang = LanguageManager.getLanguage();
const tpl = document.getElementById('subscription-data');
const sj = tpl ? tpl.getAttribute('data-subjson-url') : '';
if (sj) this.app.subJsonUrl = sj;
drawQR(this.app.subUrl);
try {
const elJson = document.getElementById('qrcode-subjson');
if (elJson && this.app.subJsonUrl) {
new QRious({ element: elJson, value: this.app.subJsonUrl, size: 220 });
}
} catch (e) { /* ignore */ }
this._onResize = () => { this.viewportWidth = window.innerWidth; };
window.addEventListener('resize', this._onResize);
},
beforeDestroy() {
if (this._onResize) window.removeEventListener('resize', this._onResize);
},
computed: {
isMobile() {
return this.viewportWidth < 576;
},
isUnlimited() {
return !this.app.totalByte;
},
isActive() {
const now = Date.now();
const expiryOk = !this.app.expireMs || this.app.expireMs >= now;
const trafficOk = !this.app.totalByte || (this.app.uploadByte + this.app.downloadByte) <= this.app.totalByte;
return expiryOk && trafficOk;
},
shadowrocketUrl() {
const rawUrl = this.app.subUrl + '?flag=shadowrocket';
const base64Url = btoa(rawUrl);
const remark = encodeURIComponent(this.app.sId || 'Subscription');
return `shadowrocket://add/sub/${base64Url}?remark=${remark}`;
},
v2boxUrl() {
return `v2box://install-sub?url=${encodeURIComponent(this.app.subUrl)}&name=${encodeURIComponent(this.app.sId)}`;
},
streisandUrl() {
return `streisand://import/${encodeURIComponent(this.app.subUrl)}`;
},
v2raytunUrl() {
return this.app.subUrl;
},
npvtunUrl() {
return this.app.subUrl;
},
happUrl() {
return `happ://add/${encodeURIComponent(this.app.subUrl)}`;
}
},
methods: {
renderLink,
copy,
open,
linkName,
i18nLabel(key) {
return '{{ i18n "' + key + '" }}';
},
},
});
})();

View File

@@ -1,194 +0,0 @@
const ONE_KB = 1024;
const ONE_MB = ONE_KB * 1024;
const ONE_GB = ONE_MB * 1024;
const ONE_TB = ONE_GB * 1024;
const ONE_PB = ONE_TB * 1024;
function sizeFormat(size) {
if (size < 0) {
return "0 B";
} else if (size < ONE_KB) {
return size.toFixed(0) + " B";
} else if (size < ONE_MB) {
return (size / ONE_KB).toFixed(2) + " KB";
} else if (size < ONE_GB) {
return (size / ONE_MB).toFixed(2) + " MB";
} else if (size < ONE_TB) {
return (size / ONE_GB).toFixed(2) + " GB";
} else if (size < ONE_PB) {
return (size / ONE_TB).toFixed(2) + " TB";
} else {
return (size / ONE_PB).toFixed(2) + " PB";
}
}
function cpuSpeedFormat(speed) {
if (speed > 1000) {
const GHz = speed / 1000;
return GHz.toFixed(2) + " GHz";
} else {
return speed.toFixed(2) + " MHz";
}
}
function cpuCoreFormat(cores) {
if (cores === 1) {
return "1 Core";
} else {
return cores + " Cores";
}
}
function base64(str) {
return Base64.encode(str);
}
function safeBase64(str) {
return base64(str)
.replace(/\+/g, '-')
.replace(/=/g, '')
.replace(/\//g, '_');
}
function formatSecond(second) {
if (second < 60) {
return second.toFixed(0) + 's';
} else if (second < 3600) {
return (second / 60).toFixed(0) + 'm';
} else if (second < 3600 * 24) {
return (second / 3600).toFixed(0) + 'h';
} else {
day = Math.floor(second / 3600 / 24);
remain = ((second/3600) - (day*24)).toFixed(0);
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
}
}
function addZero(num) {
if (num < 10) {
return "0" + num;
} else {
return num;
}
}
function toFixed(num, n) {
n = Math.pow(10, n);
return Math.floor(num * n) / n;
}
function debounce(fn, delay) {
var timeoutID = null;
return function () {
clearTimeout(timeoutID);
var args = arguments;
var that = this;
timeoutID = setTimeout(function () {
fn.apply(that, args);
}, delay);
};
}
function getCookie(cname) {
let name = cname + '=';
let ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
// decode cookie value only
return decodeURIComponent(c.substring(name.length, c.length));
}
}
return '';
}
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
let expires = 'expires=' + d.toUTCString();
// encode cookie value
document.cookie = cname + '=' + encodeURIComponent(cvalue) + ';' + expires + ';path=/';
}
function usageColor(data, threshold, total) {
switch (true) {
case data === null:
return "purple";
case total < 0:
return "green";
case total == 0:
return "purple";
case data < total - threshold:
return "green";
case data < total:
return "orange";
default:
return "red";
}
}
function clientUsageColor(clientStats, trafficDiff) {
switch (true) {
case !clientStats || clientStats.total == 0:
return "#7a316f"; // purple
case clientStats.up + clientStats.down < clientStats.total - trafficDiff:
return "#008771"; // Green
case clientStats.up + clientStats.down < clientStats.total:
return "#f37b24"; // Orange
default:
return "#cf3c3c"; // Red
}
}
function userExpiryColor(threshold, client, isDark = false) {
if (!client.enable) {
return isDark ? '#2c3950' : '#bcbcbc';
}
now = new Date().getTime(),
expiry = client.expiryTime;
switch (true) {
case expiry === null:
return "#7a316f"; // purple
case expiry < 0:
return "#008771"; // Green
case expiry == 0:
return "#7a316f"; // purple
case now < expiry - threshold:
return "#008771"; // Green
case now < expiry:
return "#f37b24"; // Orange
default:
return "#cf3c3c"; // Red
}
}
function doAllItemsExist(array1, array2) {
for (let i = 0; i < array1.length; i++) {
if (!array2.includes(array1[i])) {
return false;
}
}
return true;
}
function buildURL({ host, port, isTLS, base, path }) {
if (!host || host.length === 0) host = window.location.hostname;
if (!port || port.length === 0) port = window.location.port;
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
const protocol = isTLS ? "https:" : "http:";
port = String(port);
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
port = "";
} else {
port = `:${port}`;
}
return `${protocol}//${host}${port}${base}${path}`;
}

View File

@@ -108,14 +108,14 @@ Date.prototype.setMaxTime = function () {
* Formatting date
*/
Date.prototype.formatDate = function () {
return this.getFullYear() + "-" + addZero(this.getMonth() + 1) + "-" + addZero(this.getDate());
return this.getFullYear() + "-" + NumberFormatter.addZero(this.getMonth() + 1) + "-" + NumberFormatter.addZero(this.getDate());
};
/**
* Format time
*/
Date.prototype.formatTime = function () {
return addZero(this.getHours()) + ":" + addZero(this.getMinutes()) + ":" + addZero(this.getSeconds());
return NumberFormatter.addZero(this.getHours()) + ":" + NumberFormatter.addZero(this.getMinutes()) + ":" + NumberFormatter.addZero(this.getSeconds());
};
/**
@@ -128,13 +128,13 @@ Date.prototype.formatDateTime = function (split = ' ') {
};
class DateUtil {
// String string to date object
// String to date object
static parseDate(str) {
return new Date(str.replace(/-/g, '/'));
}
static formatMillis(millis) {
return moment(millis).format('YYYY-M-D H:m:s');
return moment(millis).format('YYYY-M-D HH:mm:ss');
}
static firstDayOfMonth() {
@@ -143,4 +143,9 @@ class DateUtil {
date.setMinTime();
return date;
}
static convertToJalalian(date) {
return date && moment.isMoment(date) ? date.format('jYYYY/jMM/jDD HH:mm:ss') : null;
}
}

885
web/assets/js/util/index.js Normal file
View File

@@ -0,0 +1,885 @@
class Msg {
constructor(success = false, msg = "", obj = null) {
this.success = success;
this.msg = msg;
this.obj = obj;
}
}
class HttpUtil {
static _handleMsg(msg) {
if (!(msg instanceof Msg) || msg.msg === "") {
return;
}
const messageType = msg.success ? 'success' : 'error';
Vue.prototype.$message[messageType](msg.msg);
}
static _respToMsg(resp) {
if (!resp || !resp.data) {
return new Msg(false, 'No response data');
}
const { data } = resp;
if (data == null) {
return new Msg(true);
}
if (typeof data === 'object' && 'success' in data) {
return new Msg(data.success, data.msg, data.obj);
}
return typeof data === 'object' ? data : new Msg(false, 'unknown data:', data);
}
static async get(url, params, options = {}) {
try {
const resp = await axios.get(url, { params, ...options });
const msg = this._respToMsg(resp);
this._handleMsg(msg);
return msg;
} catch (error) {
console.error('GET request failed:', error);
const errorMsg = new Msg(false, error.response?.data?.message || error.message || 'Request failed');
this._handleMsg(errorMsg);
return errorMsg;
}
}
static async post(url, data, options = {}) {
try {
const resp = await axios.post(url, data, options);
const msg = this._respToMsg(resp);
this._handleMsg(msg);
return msg;
} catch (error) {
console.error('POST request failed:', error);
const errorMsg = new Msg(false, error.response?.data?.message || error.message || 'Request failed');
this._handleMsg(errorMsg);
return errorMsg;
}
}
static async postWithModal(url, data, modal) {
if (modal) {
modal.loading(true);
}
const msg = await this.post(url, data);
if (modal) {
modal.loading(false);
if (msg instanceof Msg && msg.success) {
modal.close();
}
}
return msg;
}
}
class PromiseUtil {
static async sleep(timeout) {
await new Promise(resolve => {
setTimeout(resolve, timeout)
});
}
}
class RandomUtil {
static getSeq({ type = "default", hasNumbers = true, hasLowercase = true, hasUppercase = true } = {}) {
let seq = '';
switch (type) {
case "hex":
seq += "0123456789abcdef";
break;
default:
if (hasNumbers) seq += "0123456789";
if (hasLowercase) seq += "abcdefghijklmnopqrstuvwxyz";
if (hasUppercase) seq += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
break;
}
return seq;
}
static randomInteger(min, max) {
const range = max - min + 1;
const randomBuffer = new Uint32Array(1);
window.crypto.getRandomValues(randomBuffer);
return Math.floor((randomBuffer[0] / (0xFFFFFFFF + 1)) * range) + min;
}
static randomSeq(count, options = {}) {
const seq = this.getSeq(options);
const seqLength = seq.length;
const randomValues = new Uint32Array(count);
window.crypto.getRandomValues(randomValues);
return Array.from(randomValues, v => seq[v % seqLength]).join('');
}
static randomShortIds() {
const lengths = [2, 4, 6, 8, 10, 12, 14, 16].sort(() => Math.random() - 0.5);
return lengths.map(len => this.randomSeq(len, { type: "hex" })).join(',');
}
static randomLowerAndNum(len) {
return this.randomSeq(len, { hasUppercase: false });
}
static randomUUID() {
if (window.location.protocol === "https:") {
return window.crypto.randomUUID();
} else {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function (c) {
const randomValues = new Uint8Array(1);
window.crypto.getRandomValues(randomValues);
let randomValue = randomValues[0] % 16;
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
return calculatedValue.toString(16);
});
}
}
static randomShadowsocksPassword(method = SSMethods.BLAKE3_AES_256_GCM) {
let length = 32;
if ([SSMethods.BLAKE3_AES_128_GCM].includes(method)) {
length = 16;
}
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
return Base64.alternativeEncode(String.fromCharCode(...array));
}
static randomBase32String(length = 16) {
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let result = '';
let bits = 0;
let buffer = 0;
for (let i = 0; i < array.length; i++) {
buffer = (buffer << 8) | array[i];
bits += 8;
while (bits >= 5) {
bits -= 5;
result += base32Chars[(buffer >>> bits) & 0x1F];
}
}
if (bits > 0) {
result += base32Chars[(buffer << (5 - bits)) & 0x1F];
}
return result;
}
}
class ObjectUtil {
static getPropIgnoreCase(obj, prop) {
for (const name in obj) {
if (!obj.hasOwnProperty(name)) {
continue;
}
if (name.toLowerCase() === prop.toLowerCase()) {
return obj[name];
}
}
return undefined;
}
static deepSearch(obj, key) {
if (obj instanceof Array) {
for (let i = 0; i < obj.length; ++i) {
if (this.deepSearch(obj[i], key)) {
return true;
}
}
} else if (obj instanceof Object) {
for (let name in obj) {
if (!obj.hasOwnProperty(name)) {
continue;
}
if (this.deepSearch(obj[name], key)) {
return true;
}
}
} else {
return this.isEmpty(obj) ? false : obj.toString().toLowerCase().indexOf(key.toLowerCase()) >= 0;
}
return false;
}
static isEmpty(obj) {
return obj === null || obj === undefined || obj === '';
}
static isArrEmpty(arr) {
return !this.isEmpty(arr) && arr.length === 0;
}
static copyArr(dest, src) {
dest.splice(0);
for (const item of src) {
dest.push(item);
}
}
static clone(obj) {
let newObj;
if (obj instanceof Array) {
newObj = [];
this.copyArr(newObj, obj);
} else if (obj instanceof Object) {
newObj = {};
for (const key of Object.keys(obj)) {
newObj[key] = obj[key];
}
} else {
newObj = obj;
}
return newObj;
}
static deepClone(obj) {
let newObj;
if (obj instanceof Array) {
newObj = [];
for (const item of obj) {
newObj.push(this.deepClone(item));
}
} else if (obj instanceof Object) {
newObj = {};
for (const key of Object.keys(obj)) {
newObj[key] = this.deepClone(obj[key]);
}
} else {
newObj = obj;
}
return newObj;
}
static cloneProps(dest, src, ...ignoreProps) {
if (dest == null || src == null) {
return;
}
const ignoreEmpty = this.isArrEmpty(ignoreProps);
for (const key of Object.keys(src)) {
if (!src.hasOwnProperty(key)) {
continue;
} else if (!dest.hasOwnProperty(key)) {
continue;
} else if (src[key] === undefined) {
continue;
}
if (ignoreEmpty) {
dest[key] = src[key];
} else {
let ignore = false;
for (let i = 0; i < ignoreProps.length; ++i) {
if (key === ignoreProps[i]) {
ignore = true;
break;
}
}
if (!ignore) {
dest[key] = src[key];
}
}
}
}
static delProps(obj, ...props) {
for (const prop of props) {
if (prop in obj) {
delete obj[prop];
}
}
}
static execute(func, ...args) {
if (!this.isEmpty(func) && typeof func === 'function') {
func(...args);
}
}
static orDefault(obj, defaultValue) {
if (obj == null) {
return defaultValue;
}
return obj;
}
static equals(a, b) {
// shallow, symmetric comparison so newly added fields also affect equality
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
if (aKeys.length !== bKeys.length) return false;
for (const key of aKeys) {
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
if (a[key] !== b[key]) return false;
}
return true;
}
}
class Wireguard {
static gf(init) {
var r = new Float64Array(16);
if (init) {
for (var i = 0; i < init.length; ++i)
r[i] = init[i];
}
return r;
}
static pack(o, n) {
var b, m = this.gf(), t = this.gf();
for (var i = 0; i < 16; ++i)
t[i] = n[i];
this.carry(t);
this.carry(t);
this.carry(t);
for (var j = 0; j < 2; ++j) {
m[0] = t[0] - 0xffed;
for (var i = 1; i < 15; ++i) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
this.cswap(t, m, 1 - b);
}
for (var i = 0; i < 16; ++i) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
static carry(o) {
var c;
for (var i = 0; i < 16; ++i) {
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
o[i] &= 0xffff;
}
}
static cswap(p, q, b) {
var t, c = ~(b - 1);
for (var i = 0; i < 16; ++i) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
static add(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] + b[i]) | 0;
}
static subtract(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] - b[i]) | 0;
}
static multmod(o, a, b) {
var t = new Float64Array(31);
for (var i = 0; i < 16; ++i) {
for (var j = 0; j < 16; ++j)
t[i + j] += a[i] * b[j];
}
for (var i = 0; i < 15; ++i)
t[i] += 38 * t[i + 16];
for (var i = 0; i < 16; ++i)
o[i] = t[i];
this.carry(o);
this.carry(o);
}
static invert(o, i) {
var c = this.gf();
for (var a = 0; a < 16; ++a)
c[a] = i[a];
for (var a = 253; a >= 0; --a) {
this.multmod(c, c, c);
if (a !== 2 && a !== 4)
this.multmod(c, c, i);
}
for (var a = 0; a < 16; ++a)
o[a] = c[a];
}
static clamp(z) {
z[31] = (z[31] & 127) | 64;
z[0] &= 248;
}
static generatePublicKey(privateKey) {
var r, z = new Uint8Array(32);
var a = this.gf([1]),
b = this.gf([9]),
c = this.gf(),
d = this.gf([1]),
e = this.gf(),
f = this.gf(),
_121665 = this.gf([0xdb41, 1]),
_9 = this.gf([9]);
for (var i = 0; i < 32; ++i)
z[i] = privateKey[i];
this.clamp(z);
for (var i = 254; i >= 0; --i) {
r = (z[i >>> 3] >>> (i & 7)) & 1;
this.cswap(a, b, r);
this.cswap(c, d, r);
this.add(e, a, c);
this.subtract(a, a, c);
this.add(c, b, d);
this.subtract(b, b, d);
this.multmod(d, e, e);
this.multmod(f, a, a);
this.multmod(a, c, a);
this.multmod(c, b, e);
this.add(e, a, c);
this.subtract(a, a, c);
this.multmod(b, a, a);
this.subtract(c, d, f);
this.multmod(a, c, _121665);
this.add(a, a, d);
this.multmod(c, c, a);
this.multmod(a, d, f);
this.multmod(d, b, _9);
this.multmod(b, e, e);
this.cswap(a, b, r);
this.cswap(c, d, r);
}
this.invert(c, c);
this.multmod(a, a, c);
this.pack(z, a);
return z;
}
static generatePresharedKey() {
var privateKey = new Uint8Array(32);
window.crypto.getRandomValues(privateKey);
return privateKey;
}
static generatePrivateKey() {
var privateKey = this.generatePresharedKey();
this.clamp(privateKey);
return privateKey;
}
static encodeBase64(dest, src) {
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
for (var i = 0; i < 4; ++i)
dest[i] = input[i] + 65 +
(((25 - input[i]) >> 8) & 6) -
(((51 - input[i]) >> 8) & 75) -
(((61 - input[i]) >> 8) & 15) +
(((62 - input[i]) >> 8) & 3);
}
static keyToBase64(key) {
var i, base64 = new Uint8Array(44);
for (i = 0; i < 32 / 3; ++i)
this.encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
this.encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
base64[43] = 61;
return String.fromCharCode.apply(null, base64);
}
static keyFromBase64(encoded) {
const binaryStr = atob(encoded);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes;
}
static generateKeypair(secretKey = '') {
var privateKey = secretKey.length > 0 ? this.keyFromBase64(secretKey) : this.generatePrivateKey();
var publicKey = this.generatePublicKey(privateKey);
return {
publicKey: this.keyToBase64(publicKey),
privateKey: secretKey.length > 0 ? secretKey : this.keyToBase64(privateKey)
};
}
}
class ClipboardManager {
static copyText(content = "") {
// !! here old way of copying is used because not everyone can afford https connection
return new Promise((resolve) => {
try {
const textarea = window.document.createElement('textarea');
textarea.style.fontSize = '12pt';
textarea.style.border = '0';
textarea.style.padding = '0';
textarea.style.margin = '0';
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
textarea.style.top = `${window.pageYOffset || document.documentElement.scrollTop}px`;
textarea.setAttribute('readonly', '');
textarea.value = content;
window.document.body.appendChild(textarea);
textarea.select();
window.document.execCommand("copy");
window.document.body.removeChild(textarea);
resolve(true)
} catch {
resolve(false)
}
})
}
}
class Base64 {
static encode(content = "", safe = false) {
if (safe) {
return Base64.encode(content)
.replace(/\+/g, '-')
.replace(/=/g, '')
.replace(/\//g, '_')
}
return window.btoa(
String.fromCharCode(...new TextEncoder().encode(content))
)
}
static alternativeEncode(content) {
return window.btoa(
content
)
}
static decode(content = "") {
return new TextDecoder()
.decode(
Uint8Array.from(window.atob(content), c => c.charCodeAt(0))
)
}
}
class SizeFormatter {
static ONE_KB = 1024;
static ONE_MB = this.ONE_KB * 1024;
static ONE_GB = this.ONE_MB * 1024;
static ONE_TB = this.ONE_GB * 1024;
static ONE_PB = this.ONE_TB * 1024;
static sizeFormat(size) {
if (size <= 0) return "0 B";
if (size < this.ONE_KB) return size.toFixed(0) + " B";
if (size < this.ONE_MB) return (size / this.ONE_KB).toFixed(2) + " KB";
if (size < this.ONE_GB) return (size / this.ONE_MB).toFixed(2) + " MB";
if (size < this.ONE_TB) return (size / this.ONE_GB).toFixed(2) + " GB";
if (size < this.ONE_PB) return (size / this.ONE_TB).toFixed(2) + " TB";
return (size / this.ONE_PB).toFixed(2) + " PB";
}
}
class CPUFormatter {
static cpuSpeedFormat(speed) {
return speed > 1000 ? (speed / 1000).toFixed(2) + " GHz" : speed.toFixed(2) + " MHz";
}
static cpuCoreFormat(cores) {
return cores === 1 ? "1 Core" : cores + " Cores";
}
}
class TimeFormatter {
static formatSecond(second) {
if (second < 60) return second.toFixed(0) + 's';
if (second < 3600) return (second / 60).toFixed(0) + 'm';
if (second < 3600 * 24) return (second / 3600).toFixed(0) + 'h';
let day = Math.floor(second / 3600 / 24);
let remain = ((second / 3600) - (day * 24)).toFixed(0);
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
}
}
class NumberFormatter {
static addZero(num) {
return num < 10 ? "0" + num : num;
}
static toFixed(num, n) {
n = Math.pow(10, n);
return Math.floor(num * n) / n;
}
}
class Utils {
static debounce(fn, delay) {
let timeoutID = null;
return function () {
clearTimeout(timeoutID);
let args = arguments;
let that = this;
timeoutID = setTimeout(() => fn.apply(that, args), delay);
};
}
}
class CookieManager {
static getCookie(cname) {
let name = cname + '=';
let ca = document.cookie.split(';');
for (let c of ca) {
c = c.trim();
if (c.indexOf(name) === 0) {
return decodeURIComponent(c.substring(name.length, c.length));
}
}
return '';
}
static setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
let expires = 'expires=' + d.toUTCString();
document.cookie = cname + '=' + encodeURIComponent(cvalue) + ';' + expires + ';path=/';
}
}
class ColorUtils {
static usageColor(data, threshold, total) {
switch (true) {
case data === null: return "purple";
case total < 0: return "green";
case total == 0: return "purple";
case data < total - threshold: return "green";
case data < total: return "orange";
default: return "red";
}
}
static clientUsageColor(clientStats, trafficDiff) {
switch (true) {
case !clientStats || clientStats.total == 0: return "#7a316f";
case clientStats.up + clientStats.down < clientStats.total - trafficDiff: return "#008771";
case clientStats.up + clientStats.down < clientStats.total: return "#f37b24";
default: return "#cf3c3c";
}
}
static userExpiryColor(threshold, client, isDark = false) {
if (!client.enable) return isDark ? '#2c3950' : '#bcbcbc';
let now = new Date().getTime(), expiry = client.expiryTime;
switch (true) {
case expiry === null: return "#7a316f";
case expiry < 0: return "#008771";
case expiry == 0: return "#7a316f";
case now < expiry - threshold: return "#008771";
case now < expiry: return "#f37b24";
default: return "#cf3c3c";
}
}
}
class ArrayUtils {
static doAllItemsExist(array1, array2) {
return array1.every(item => array2.includes(item));
}
}
class URLBuilder {
static buildURL({ host, port, isTLS, base, path }) {
if (!host || host.length === 0) host = window.location.hostname;
if (!port || port.length === 0) port = window.location.port;
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
const protocol = isTLS ? "https:" : "http:";
port = String(port);
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
port = "";
} else {
port = `:${port}`;
}
return `${protocol}//${host}${port}${base}${path}`;
}
}
class LanguageManager {
static supportedLanguages = [
{
name: "العربية",
value: "ar-EG",
icon: "🇪🇬",
},
{
name: "English",
value: "en-US",
icon: "🇺🇸",
},
{
name: "فارسی",
value: "fa-IR",
icon: "🇮🇷",
},
{
name: "简体中文",
value: "zh-CN",
icon: "🇨🇳",
},
{
name: "繁體中文",
value: "zh-TW",
icon: "🇹🇼",
},
{
name: "日本語",
value: "ja-JP",
icon: "🇯🇵",
},
{
name: "Русский",
value: "ru-RU",
icon: "🇷🇺",
},
{
name: "Tiếng Việt",
value: "vi-VN",
icon: "🇻🇳",
},
{
name: "Español",
value: "es-ES",
icon: "🇪🇸",
},
{
name: "Indonesian",
value: "id-ID",
icon: "🇮🇩",
},
{
name: "Український",
value: "uk-UA",
icon: "🇺🇦",
},
{
name: "Türkçe",
value: "tr-TR",
icon: "🇹🇷",
},
{
name: "Português",
value: "pt-BR",
icon: "🇧🇷",
}
]
static getLanguage() {
let lang = CookieManager.getCookie("lang");
if (!lang) {
if (window.navigator) {
lang = window.navigator.language || window.navigator.userLanguage;
const simularLangs = [
["ar", this.supportedLanguages[0].value],
["fa", this.supportedLanguages[2].value],
["ja", this.supportedLanguages[5].value],
["ru", this.supportedLanguages[6].value],
["vi", this.supportedLanguages[7].value],
["es", this.supportedLanguages[8].value],
["id", this.supportedLanguages[9].value],
["uk", this.supportedLanguages[10].value],
["tr", this.supportedLanguages[11].value],
["pt", this.supportedLanguages[12].value],
]
simularLangs.forEach((pair) => {
if (lang === pair[0]) {
lang = pair[1];
}
});
if (LanguageManager.isSupportLanguage(lang)) {
CookieManager.setCookie("lang", lang, 150);
} else {
CookieManager.setCookie("lang", "en-US", 150);
window.location.reload();
}
} else {
CookieManager.setCookie("lang", "en-US", 150);
window.location.reload();
}
}
return lang;
}
static setLanguage(language) {
if (!LanguageManager.isSupportLanguage(language)) {
language = "en-US";
}
CookieManager.setCookie("lang", language, 150);
window.location.reload();
}
static isSupportLanguage(language) {
const languageFilter = LanguageManager.supportedLanguages.filter((lang) => {
return lang.value === language
})
return languageFilter.length > 0;
}
}
const MediaQueryMixin = {
data() {
return {
isMobile: window.innerWidth <= 768,
};
},
methods: {
updateDeviceType() {
this.isMobile = window.innerWidth <= 768;
},
},
mounted() {
window.addEventListener('resize', this.updateDeviceType);
},
beforeDestroy() {
window.removeEventListener('resize', this.updateDeviceType);
},
}
class FileManager {
static downloadTextFile(content, filename = 'file.txt', options = { type: "text/plain" }) {
let link = window.document.createElement('a');
link.download = filename;
link.style.border = '0';
link.style.padding = '0';
link.style.margin = '0';
link.style.position = 'absolute';
link.style.left = '-9999px';
link.style.top = `${window.pageYOffset || window.document.documentElement.scrollTop}px`;
link.href = URL.createObjectURL(new Blob([content], options));
link.click();
URL.revokeObjectURL(link.href);
link.remove();
}
}

View File

@@ -1,484 +0,0 @@
class Msg {
constructor(success, msg, obj) {
this.success = false;
this.msg = "";
this.obj = null;
if (success != null) {
this.success = success;
}
if (msg != null) {
this.msg = msg;
}
if (obj != null) {
this.obj = obj;
}
}
}
class HttpUtil {
static _handleMsg(msg) {
if (!(msg instanceof Msg)) {
return;
}
if (msg.msg === "") {
return;
}
if (msg.success) {
Vue.prototype.$message.success(msg.msg);
} else {
Vue.prototype.$message.error(msg.msg);
}
}
static _respToMsg(resp) {
const data = resp.data;
if (data == null) {
return new Msg(true);
} else if (typeof data === 'object') {
if (data.hasOwnProperty('success')) {
return new Msg(data.success, data.msg, data.obj);
} else {
return data;
}
} else {
return new Msg(false, 'unknown data:', data);
}
}
static async get(url, data, options) {
let msg;
try {
const resp = await axios.get(url, data, options);
msg = this._respToMsg(resp);
} catch (e) {
msg = new Msg(false, e.toString());
}
this._handleMsg(msg);
return msg;
}
static async post(url, data, options) {
let msg;
try {
const resp = await axios.post(url, data, options);
msg = this._respToMsg(resp);
} catch (e) {
msg = new Msg(false, e.toString());
}
this._handleMsg(msg);
return msg;
}
static async postWithModal(url, data, modal) {
if (modal) {
modal.loading(true);
}
const msg = await this.post(url, data);
if (modal) {
modal.loading(false);
if (msg instanceof Msg && msg.success) {
modal.close();
}
}
return msg;
}
}
class PromiseUtil {
static async sleep(timeout) {
await new Promise(resolve => {
setTimeout(resolve, timeout)
});
}
}
const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
class RandomUtil {
static randomIntRange(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
static randomInt(n) {
return this.randomIntRange(0, n);
}
static randomSeq(count) {
let str = '';
for (let i = 0; i < count; ++i) {
str += seq[this.randomInt(62)];
}
return str;
}
static randomShortId() {
let str = '';
for (let i = 0; i < 8; ++i) {
str += seq[this.randomInt(16)];
}
return str;
}
static randomLowerAndNum(len) {
let str = '';
for (let i = 0; i < len; ++i) {
str += seq[this.randomInt(36)];
}
return str;
}
static randomUUID() {
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return template.replace(/[xy]/g, function (c) {
const randomValues = new Uint8Array(1);
crypto.getRandomValues(randomValues);
let randomValue = randomValues[0] % 16;
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
return calculatedValue.toString(16);
});
}
static randomShadowsocksPassword() {
let array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return btoa(String.fromCharCode.apply(null, array));
}
}
class ObjectUtil {
static getPropIgnoreCase(obj, prop) {
for (const name in obj) {
if (!obj.hasOwnProperty(name)) {
continue;
}
if (name.toLowerCase() === prop.toLowerCase()) {
return obj[name];
}
}
return undefined;
}
static deepSearch(obj, key) {
if (obj instanceof Array) {
for (let i = 0; i < obj.length; ++i) {
if (this.deepSearch(obj[i], key)) {
return true;
}
}
} else if (obj instanceof Object) {
for (let name in obj) {
if (!obj.hasOwnProperty(name)) {
continue;
}
if (this.deepSearch(obj[name], key)) {
return true;
}
}
} else {
return this.isEmpty(obj) ? false : obj.toString().toLowerCase().indexOf(key.toLowerCase()) >= 0;
}
return false;
}
static isEmpty(obj) {
return obj === null || obj === undefined || obj === '';
}
static isArrEmpty(arr) {
return !this.isEmpty(arr) && arr.length === 0;
}
static copyArr(dest, src) {
dest.splice(0);
for (const item of src) {
dest.push(item);
}
}
static clone(obj) {
let newObj;
if (obj instanceof Array) {
newObj = [];
this.copyArr(newObj, obj);
} else if (obj instanceof Object) {
newObj = {};
for (const key of Object.keys(obj)) {
newObj[key] = obj[key];
}
} else {
newObj = obj;
}
return newObj;
}
static deepClone(obj) {
let newObj;
if (obj instanceof Array) {
newObj = [];
for (const item of obj) {
newObj.push(this.deepClone(item));
}
} else if (obj instanceof Object) {
newObj = {};
for (const key of Object.keys(obj)) {
newObj[key] = this.deepClone(obj[key]);
}
} else {
newObj = obj;
}
return newObj;
}
static cloneProps(dest, src, ...ignoreProps) {
if (dest == null || src == null) {
return;
}
const ignoreEmpty = this.isArrEmpty(ignoreProps);
for (const key of Object.keys(src)) {
if (!src.hasOwnProperty(key)) {
continue;
} else if (!dest.hasOwnProperty(key)) {
continue;
} else if (src[key] === undefined) {
continue;
}
if (ignoreEmpty) {
dest[key] = src[key];
} else {
let ignore = false;
for (let i = 0; i < ignoreProps.length; ++i) {
if (key === ignoreProps[i]) {
ignore = true;
break;
}
}
if (!ignore) {
dest[key] = src[key];
}
}
}
}
static delProps(obj, ...props) {
for (const prop of props) {
if (prop in obj) {
delete obj[prop];
}
}
}
static execute(func, ...args) {
if (!this.isEmpty(func) && typeof func === 'function') {
func(...args);
}
}
static orDefault(obj, defaultValue) {
if (obj == null) {
return defaultValue;
}
return obj;
}
static equals(a, b) {
for (const key in a) {
if (!a.hasOwnProperty(key)) {
continue;
}
if (!b.hasOwnProperty(key)) {
return false;
} else if (a[key] !== b[key]) {
return false;
}
}
return true;
}
}
class Wireguard {
static gf(init) {
var r = new Float64Array(16);
if (init) {
for (var i = 0; i < init.length; ++i)
r[i] = init[i];
}
return r;
}
static pack(o, n) {
var b, m = this.gf(), t = this.gf();
for (var i = 0; i < 16; ++i)
t[i] = n[i];
this.carry(t);
this.carry(t);
this.carry(t);
for (var j = 0; j < 2; ++j) {
m[0] = t[0] - 0xffed;
for (var i = 1; i < 15; ++i) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
this.cswap(t, m, 1 - b);
}
for (var i = 0; i < 16; ++i) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
static carry(o) {
var c;
for (var i = 0; i < 16; ++i) {
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
o[i] &= 0xffff;
}
}
static cswap(p, q, b) {
var t, c = ~(b - 1);
for (var i = 0; i < 16; ++i) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
static add(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] + b[i]) | 0;
}
static subtract(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] - b[i]) | 0;
}
static multmod(o, a, b) {
var t = new Float64Array(31);
for (var i = 0; i < 16; ++i) {
for (var j = 0; j < 16; ++j)
t[i + j] += a[i] * b[j];
}
for (var i = 0; i < 15; ++i)
t[i] += 38 * t[i + 16];
for (var i = 0; i < 16; ++i)
o[i] = t[i];
this.carry(o);
this.carry(o);
}
static invert(o, i) {
var c = this.gf();
for (var a = 0; a < 16; ++a)
c[a] = i[a];
for (var a = 253; a >= 0; --a) {
this.multmod(c, c, c);
if (a !== 2 && a !== 4)
this.multmod(c, c, i);
}
for (var a = 0; a < 16; ++a)
o[a] = c[a];
}
static clamp(z) {
z[31] = (z[31] & 127) | 64;
z[0] &= 248;
}
static generatePublicKey(privateKey) {
var r, z = new Uint8Array(32);
var a = this.gf([1]),
b = this.gf([9]),
c = this.gf(),
d = this.gf([1]),
e = this.gf(),
f = this.gf(),
_121665 = this.gf([0xdb41, 1]),
_9 = this.gf([9]);
for (var i = 0; i < 32; ++i)
z[i] = privateKey[i];
this.clamp(z);
for (var i = 254; i >= 0; --i) {
r = (z[i >>> 3] >>> (i & 7)) & 1;
this.cswap(a, b, r);
this.cswap(c, d, r);
this.add(e, a, c);
this.subtract(a, a, c);
this.add(c, b, d);
this.subtract(b, b, d);
this.multmod(d, e, e);
this.multmod(f, a, a);
this.multmod(a, c, a);
this.multmod(c, b, e);
this.add(e, a, c);
this.subtract(a, a, c);
this.multmod(b, a, a);
this.subtract(c, d, f);
this.multmod(a, c, _121665);
this.add(a, a, d);
this.multmod(c, c, a);
this.multmod(a, d, f);
this.multmod(d, b, _9);
this.multmod(b, e, e);
this.cswap(a, b, r);
this.cswap(c, d, r);
}
this.invert(c, c);
this.multmod(a, a, c);
this.pack(z, a);
return z;
}
static generatePresharedKey() {
var privateKey = new Uint8Array(32);
window.crypto.getRandomValues(privateKey);
return privateKey;
}
static generatePrivateKey() {
var privateKey = this.generatePresharedKey();
this.clamp(privateKey);
return privateKey;
}
static encodeBase64(dest, src) {
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
for (var i = 0; i < 4; ++i)
dest[i] = input[i] + 65 +
(((25 - input[i]) >> 8) & 6) -
(((51 - input[i]) >> 8) & 75) -
(((61 - input[i]) >> 8) & 15) +
(((62 - input[i]) >> 8) & 3);
}
static keyToBase64(key) {
var i, base64 = new Uint8Array(44);
for (i = 0; i < 32 / 3; ++i)
this.encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
this.encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
base64[43] = 61;
return String.fromCharCode.apply(null, base64);
}
static keyFromBase64(encoded) {
const binaryStr = atob(encoded);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes;
}
static generateKeypair(secretKey='') {
var privateKey = secretKey.length>0 ? this.keyFromBase64(secretKey) : this.generatePrivateKey();
var publicKey = this.generatePublicKey(privateKey);
return {
publicKey: this.keyToBase64(publicKey),
privateKey: secretKey.length>0 ? secretKey : this.keyToBase64(privateKey)
};
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More