Compare commits
585 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5408a2f82c | ||
|
|
c8d71ea748 | ||
|
|
46de886b53 | ||
|
|
6d41320ed7 | ||
|
|
bf9d2e6aeb | ||
|
|
ed96fa090b | ||
|
|
3ac1d7f546 | ||
|
|
10025ffa66 | ||
|
|
5ee62b25ca | ||
|
|
311d11a3c1 | ||
|
|
40b6d7707a | ||
|
|
cbf316db31 | ||
|
|
33a36ada4b | ||
|
|
82ddd10627 | ||
|
|
2401c99817 | ||
|
|
2f36a4047c | ||
|
|
dc3b0d218a | ||
|
|
610d29765a | ||
|
|
b1ea8005e4 | ||
|
|
3f0bfa2472 | ||
|
|
1e2ff650ad | ||
|
|
c2d6dd923f | ||
|
|
723ec25fb2 | ||
|
|
7dc52e9a53 | ||
|
|
fe9f0d1d0e | ||
|
|
18d74d54ca | ||
|
|
c7ba6ae909 | ||
|
|
3edf79e589 | ||
|
|
5420e643cf | ||
|
|
9fcd0387ca | ||
|
|
7b039d219e | ||
|
|
dbec28b915 | ||
|
|
e5126806d7 | ||
|
|
b008ff4ad2 | ||
|
|
da6b89fdcd | ||
|
|
d7882c25d1 | ||
|
|
ed2a0a0bcf | ||
|
|
4a0914cb1e | ||
|
|
664269d513 | ||
|
|
d0796b26c9 | ||
|
|
2750f46c01 | ||
|
|
023eb513e4 | ||
|
|
0c7b59ed47 | ||
|
|
3087c1b123 | ||
|
|
2198397197 | ||
|
|
d10c312e62 | ||
|
|
24a3411465 | ||
|
|
2198e7a28f | ||
|
|
6b23b416a7 | ||
|
|
16f53ce4c2 | ||
|
|
27445b30e9 | ||
|
|
3d0212c21d | ||
|
|
978755960f | ||
|
|
9b51e9a5c5 | ||
|
|
6879a8fbcb | ||
|
|
7258841491 | ||
|
|
23dd80fbb0 | ||
|
|
6556884c7f | ||
|
|
d5c532c64f | ||
|
|
ad5f774a1e | ||
|
|
aa285914fa | ||
|
|
4d02756e1e | ||
|
|
825d93d95f | ||
|
|
5ea6386815 | ||
|
|
d064e85ecd | ||
|
|
9fc03bd10a | ||
|
|
ae08a29cde | ||
|
|
4f25eb230e | ||
|
|
ce72d53d1a | ||
|
|
5e641ff9e8 | ||
|
|
58898e5758 | ||
|
|
569550d5f6 | ||
|
|
419ea63dd0 | ||
|
|
6a17285935 | ||
|
|
4b03e9d919 | ||
|
|
7e9c3bdbaf | ||
|
|
957f3dbb54 | ||
|
|
05e60af283 | ||
|
|
5e40458116 | ||
|
|
baf6fdd29d | ||
|
|
45f78d3521 | ||
|
|
01f984e054 | ||
|
|
e4ba5ba53a | ||
|
|
6ff555c8bb | ||
|
|
3c1634ca7c | ||
|
|
561c4810be | ||
|
|
eb1b96643d | ||
|
|
de5314c01f | ||
|
|
1088d1faf3 | ||
|
|
267024c43f | ||
|
|
0d595f56e4 | ||
|
|
a4c4f9efb3 | ||
|
|
73a5722cca | ||
|
|
30264043f8 | ||
|
|
c6062eb15c | ||
|
|
f1b7944828 | ||
|
|
7a57b31ff3 | ||
|
|
6e1b949081 | ||
|
|
0ad708b1b6 | ||
|
|
71f13ebcbd | ||
|
|
f5f4a530cc | ||
|
|
702f03e4b7 | ||
|
|
487ec74e0b | ||
|
|
b4dae36345 | ||
|
|
761728255c | ||
|
|
b1ab156e42 | ||
|
|
fa45bf87de | ||
|
|
75416eebd7 | ||
|
|
87042d77ba | ||
|
|
011e0f309a | ||
|
|
b7164805f8 | ||
|
|
bbdeb65291 | ||
|
|
038cf34219 | ||
|
|
98a1517470 | ||
|
|
ce76cedb0d | ||
|
|
24a313d605 | ||
|
|
c81c27073c | ||
|
|
5d11e6e13f | ||
|
|
f3d0b92e4a | ||
|
|
c8c0e77714 | ||
|
|
49b8f46864 | ||
|
|
cad07be847 | ||
|
|
4b20f16024 | ||
|
|
d642774a44 | ||
|
|
1644904755 | ||
|
|
5c10035bd9 | ||
|
|
2e6faf69e6 | ||
|
|
f88b7b07f0 | ||
|
|
e5752239f4 | ||
|
|
cb22b4ad47 | ||
|
|
6a2e0071cf | ||
|
|
f86219f4de | ||
|
|
e272c160b1 | ||
|
|
ba50c99c10 | ||
|
|
00b61de646 | ||
|
|
dff4ad31ff | ||
|
|
13baf77893 | ||
|
|
4531574de3 | ||
|
|
d1e07954c5 | ||
|
|
d9922d93af | ||
|
|
c7d315f848 | ||
|
|
1781790dce | ||
|
|
29f950046a | ||
|
|
5dae785786 | ||
|
|
1b1cbfff42 | ||
|
|
c93467b852 | ||
|
|
c988d55256 | ||
|
|
ef625c75d8 | ||
|
|
182e591c48 | ||
|
|
3666d1193f | ||
|
|
7a5a833af3 | ||
|
|
58f978bb0a | ||
|
|
6d47496069 | ||
|
|
e5c19759db | ||
|
|
295a8b6e37 | ||
|
|
384e23aeb2 | ||
|
|
23293813bb | ||
|
|
c15ec5315a | ||
|
|
1ddfe4aba3 | ||
|
|
fe3b1c9b52 | ||
|
|
d39ccf4b8f | ||
|
|
1aed2d8cdc | ||
|
|
c3084aaece | ||
|
|
13cf7271d6 | ||
|
|
63edc63ab0 | ||
|
|
85cbad3ef4 | ||
|
|
3d54e33051 | ||
|
|
01be9fec95 | ||
|
|
0306e75c2a | ||
|
|
255ff9cc20 | ||
|
|
2fbb1ca6c9 | ||
|
|
3b47028060 | ||
|
|
d9ab8b4ce4 | ||
|
|
e6389f3fb3 | ||
|
|
96fd7d0e7c | ||
|
|
cf02f02210 | ||
|
|
4dc8974af0 | ||
|
|
b527a528ea | ||
|
|
1a53af0434 | ||
|
|
be8d55dadb | ||
|
|
d54e7a9b14 | ||
|
|
45c3d730d4 | ||
|
|
aab01ff11a | ||
|
|
236dddf482 | ||
|
|
8e472838d8 | ||
|
|
b75a1ef5e1 | ||
|
|
d956f78347 | ||
|
|
8ef447a997 | ||
|
|
520f7a2d15 | ||
|
|
3ded4ee658 | ||
|
|
bea19a263d | ||
|
|
878e0d02cd | ||
|
|
b15ea1f74d | ||
|
|
431d7350a5 | ||
|
|
127bea7f73 | ||
|
|
7c58bcbb46 | ||
|
|
fec9b25248 | ||
|
|
728166bd1a | ||
|
|
d376ce057c | ||
|
|
5e6e900e64 | ||
|
|
19f7938617 | ||
|
|
fe791b6e99 | ||
|
|
a02bf3195d | ||
|
|
3ea05d30c1 | ||
|
|
40ebf2902e | ||
|
|
14253c3586 | ||
|
|
7b15274c84 | ||
|
|
6545d8b61d | ||
|
|
6f4eefe601 | ||
|
|
510c35f450 | ||
|
|
00addb0dd9 | ||
|
|
db140a1e9b | ||
|
|
db945e2fbd | ||
|
|
667fac15f4 | ||
|
|
29033a7828 | ||
|
|
2ffde55f8f | ||
|
|
6e5ed881f2 | ||
|
|
d52c50fd9e | ||
|
|
fa5fb927c1 | ||
|
|
f7198c4c2f | ||
|
|
21e7d45b54 | ||
|
|
db62a07fb8 | ||
|
|
e3120c4028 | ||
|
|
7ae855e7c9 | ||
|
|
b9307c6c9c | ||
|
|
d30cdbf49a | ||
|
|
cac00224db | ||
|
|
b68f0a206c | ||
|
|
0bde51b91e | ||
|
|
280a22b57d | ||
|
|
315d852087 | ||
|
|
6a0d2e0a29 | ||
|
|
a811225610 | ||
|
|
1893c3814d | ||
|
|
422c391f96 | ||
|
|
f7f95ffbae | ||
|
|
f408bd7c77 | ||
|
|
ad13ce6cde | ||
|
|
c35179d924 | ||
|
|
cedc7f0fb8 | ||
|
|
64fa0e97a3 | ||
|
|
a45e9de472 | ||
|
|
101e9ebf35 | ||
|
|
17a76d2843 | ||
|
|
a23a5de540 | ||
|
|
a16e83468b | ||
|
|
1c59afe031 | ||
|
|
c49ec9a74c | ||
|
|
a0dd101d97 | ||
|
|
700cf9c10b | ||
|
|
697cd5e6d9 | ||
|
|
c6d27a4463 | ||
|
|
751f564c4a | ||
|
|
6658f648e6 | ||
|
|
d6f9f3f6d3 | ||
|
|
fad6c497eb | ||
|
|
42fa64770b | ||
|
|
0a207b8a2c | ||
|
|
26bf693dbd | ||
|
|
7483fb2ec5 | ||
|
|
2d8cca3a2e | ||
|
|
cf7fec1351 | ||
|
|
361849b9db | ||
|
|
c13db7922e | ||
|
|
d6d05a9b4d | ||
|
|
3caace2cb6 | ||
|
|
99f26be30d | ||
|
|
b0edd24c52 | ||
|
|
f0cfd48f66 | ||
|
|
f5aea03765 | ||
|
|
14cdde371f | ||
|
|
99a23f25d5 | ||
|
|
653ec90451 | ||
|
|
91a84db479 | ||
|
|
0f97eca314 | ||
|
|
fb79081aa1 | ||
|
|
ea19fb8ff6 | ||
|
|
f4cfe9eb63 | ||
|
|
4def70a006 | ||
|
|
e0e9e2681a | ||
|
|
31e1581d6b | ||
|
|
018e98a510 | ||
|
|
21ea673c30 | ||
|
|
08b55da408 | ||
|
|
7a3ee69a7f | ||
|
|
664bd9b596 | ||
|
|
ceb1217121 | ||
|
|
e754523689 | ||
|
|
e84503feec | ||
|
|
1bbf31df9f | ||
|
|
49bfff9fa5 | ||
|
|
d18a1a37ce | ||
|
|
04c6b2722b | ||
|
|
94d651fc93 | ||
|
|
aae0cb37b7 | ||
|
|
b922d986d6 | ||
|
|
8a7cffd63f | ||
|
|
c8e8c97afc | ||
|
|
46ba4c4518 | ||
|
|
a787ab497c | ||
|
|
3be204f272 | ||
|
|
de13729a97 | ||
|
|
468eb8b908 | ||
|
|
e95a748e77 | ||
|
|
34e2d961f5 | ||
|
|
b4a1d81444 | ||
|
|
46ef506aa6 | ||
|
|
51220917c4 | ||
|
|
b34956647b | ||
|
|
5c4e2dfd39 | ||
|
|
dd4c2adb37 | ||
|
|
2dec7f48f5 | ||
|
|
04cf250a54 | ||
|
|
ac9ab828b5 | ||
|
|
4dd40f6f19 | ||
|
|
7911eeb69f | ||
|
|
6e9180a665 | ||
|
|
66fe84181b | ||
|
|
7b7eb98acb | ||
|
|
7eb5afdd8d | ||
|
|
522ccda71c | ||
|
|
f780efb430 | ||
|
|
783f1a073e | ||
|
|
a4c38ec8ae | ||
|
|
0c47771671 | ||
|
|
67920a1962 | ||
|
|
49d3957c07 | ||
|
|
ee946ceab2 | ||
|
|
b650064177 | ||
|
|
9fb9d7201e | ||
|
|
4a3b9b913d | ||
|
|
da674d44cf | ||
|
|
cc3252531b | ||
|
|
284731deeb | ||
|
|
9bc5c1d070 | ||
|
|
26a7700557 | ||
|
|
b6a919218a | ||
|
|
6cc07254e0 | ||
|
|
4ad5a5aba4 | ||
|
|
7ab8164de4 | ||
|
|
195effd177 | ||
|
|
747ad3b9c8 | ||
|
|
cf879f9527 | ||
|
|
04c658f1a0 | ||
|
|
2ab1a174db | ||
|
|
dfca2af997 | ||
|
|
0a7d15b48c | ||
|
|
518bc72f90 | ||
|
|
3c65209ce9 | ||
|
|
174535b05d | ||
|
|
fff54fe7f3 | ||
|
|
0859d230b0 | ||
|
|
02998c5467 | ||
|
|
3f38c42852 | ||
|
|
49295661fd | ||
|
|
e4301f8d93 | ||
|
|
c6586f8df2 | ||
|
|
8e81008cdc | ||
|
|
e33ad809d6 | ||
|
|
d804043a18 | ||
|
|
0fb0df7056 | ||
|
|
73e90e0eaa | ||
|
|
44ef1ac9a6 | ||
|
|
aeac7f2c8b | ||
|
|
39ef172b87 | ||
|
|
0df85cc3d9 | ||
|
|
f0f4f082ae | ||
|
|
b29bd993d4 | ||
|
|
127eaf69b6 | ||
|
|
36b0289bc6 | ||
|
|
0abd0be725 | ||
|
|
918a2b1533 | ||
|
|
88a17cd227 | ||
|
|
b60387accb | ||
|
|
049177024b | ||
|
|
9c63638af1 | ||
|
|
67dfe664a6 | ||
|
|
4efcdb3e01 | ||
|
|
ddc2cfacb9 | ||
|
|
337729529a | ||
|
|
4c4cc362b3 | ||
|
|
4e0aca16c2 | ||
|
|
749a426a71 | ||
|
|
b859327b8a | ||
|
|
3e8fc59213 | ||
|
|
462e02140d | ||
|
|
6b41df2d89 | ||
|
|
eb5ed5c0dd | ||
|
|
e0bbacf013 | ||
|
|
34af7f8bfa | ||
|
|
b569c21fec | ||
|
|
c4a5c059e3 | ||
|
|
c21ed90da0 | ||
|
|
9b58277945 | ||
|
|
5a4a42aeb8 | ||
|
|
9476472bf6 | ||
|
|
2ce9c3cc81 | ||
|
|
0a8bfc2725 | ||
|
|
c5bbb6b632 | ||
|
|
f497a8dbcc | ||
|
|
4290081486 | ||
|
|
ccda652e69 | ||
|
|
2982d809ab | ||
|
|
7ad4a3dffc | ||
|
|
c0ef53e542 | ||
|
|
b7d1c84cd0 | ||
|
|
111bfe5d2e | ||
|
|
6e59aa14b0 | ||
|
|
a4cf77422f | ||
|
|
35df2a0505 | ||
|
|
9f445686a4 | ||
|
|
0fc935e996 | ||
|
|
adb08a60cf | ||
|
|
e3576e8a85 | ||
|
|
9c065aed4e | ||
|
|
7abb092211 | ||
|
|
937bfb4c78 | ||
|
|
1bcdc54b68 | ||
|
|
eb58314c53 | ||
|
|
c158e6ec73 | ||
|
|
5ae587ee81 | ||
|
|
d40fa46851 | ||
|
|
19a31686da | ||
|
|
13f7e07128 | ||
|
|
0e3691fdbd | ||
|
|
e359b5c75e | ||
|
|
3b3bd3dea4 | ||
|
|
8f36b7ea84 | ||
|
|
569d99512c | ||
|
|
ac84553a68 | ||
|
|
610db7827d | ||
|
|
088b55c9ed | ||
|
|
c800e29900 | ||
|
|
14435db0d8 | ||
|
|
bd6402562e | ||
|
|
d16ad11136 | ||
|
|
6c27e4177d | ||
|
|
bebf83f06c | ||
|
|
07bf741b15 | ||
|
|
5e5851029d | ||
|
|
e6020850fc | ||
|
|
80183f61e8 | ||
|
|
1b9432ff37 | ||
|
|
f1f813269c | ||
|
|
a23f390402 | ||
|
|
2950ce0c17 | ||
|
|
514c4909a4 | ||
|
|
99cadf7652 | ||
|
|
4ca36d64a8 | ||
|
|
863009dcaa | ||
|
|
2ef5ccc2fd | ||
|
|
744583b4e7 | ||
|
|
b36032e22c | ||
|
|
ac7901abba | ||
|
|
dff2496d73 | ||
|
|
d97d36bb9e | ||
|
|
b0d2cb93e1 | ||
|
|
f98d78c356 | ||
|
|
d85226dc79 | ||
|
|
1c2b6095c9 | ||
|
|
5f8c8f4525 | ||
|
|
6a49e99a2c | ||
|
|
6062031de4 | ||
|
|
fb2b58110d | ||
|
|
c385662783 | ||
|
|
4a05188a7f | ||
|
|
1454c4ebc5 | ||
|
|
4b1c76e972 | ||
|
|
f1f5d323e8 | ||
|
|
e37f2d3222 | ||
|
|
4f2f855c04 | ||
|
|
dcab4e6f9c | ||
|
|
e703055793 | ||
|
|
7efe1d60d5 | ||
|
|
1d84284b6d | ||
|
|
8404b33232 | ||
|
|
41d39dfaa8 | ||
|
|
761eb5f384 | ||
|
|
e72f67ca54 | ||
|
|
b8df15171e | ||
|
|
5f531f2de1 | ||
|
|
8335238eb3 | ||
|
|
aaf68ecb21 | ||
|
|
db6781f311 | ||
|
|
a85b02c6ab | ||
|
|
19a832cad8 | ||
|
|
f0dd6152fd | ||
|
|
decac2ef74 | ||
|
|
c3ce1da0d6 | ||
|
|
02bc488c1c | ||
|
|
b22f859c38 | ||
|
|
98869c7169 | ||
|
|
150c89db72 | ||
|
|
fe0a8375a3 | ||
|
|
80cfbefd75 | ||
|
|
f2ee18235f | ||
|
|
f48df1e5c0 | ||
|
|
b24855082e | ||
|
|
27434f3235 | ||
|
|
cdb6eac0e6 | ||
|
|
9e13513205 | ||
|
|
b09f52357c | ||
|
|
ac08e86747 | ||
|
|
c9c8abe97b | ||
|
|
0b8beafc89 | ||
|
|
8b6e3491c4 | ||
|
|
6cf2b56f9b | ||
|
|
19b95829e0 | ||
|
|
4bea427c79 | ||
|
|
da7e4d51d6 | ||
|
|
43713fbdf8 | ||
|
|
dc29e858c9 | ||
|
|
c30c6f08f3 | ||
|
|
7c892ac051 | ||
|
|
75dd7b93f5 | ||
|
|
d5de8e1bf3 | ||
|
|
16b4795956 | ||
|
|
e5835c299c | ||
|
|
4fdef3cfde | ||
|
|
cf6a8bd463 | ||
|
|
59a84e844c | ||
|
|
6b0c9a5fad | ||
|
|
77edea5419 | ||
|
|
521870df0a | ||
|
|
dc1c1eb998 | ||
|
|
e78427245a | ||
|
|
fbcab5bc52 | ||
|
|
89c79c3ec3 | ||
|
|
566cd9e9c4 | ||
|
|
ad78cec7c7 | ||
|
|
176ab5f48e | ||
|
|
9c4fa23931 | ||
|
|
2938694c45 | ||
|
|
88d0fb9753 | ||
|
|
d23a7f81ef | ||
|
|
2e363445fc | ||
|
|
33d983bc20 | ||
|
|
3e7c7831bc | ||
|
|
374d49eb92 | ||
|
|
663cf5649f | ||
|
|
095ebccbb0 | ||
|
|
f4bb6b0517 | ||
|
|
575234ac37 | ||
|
|
16cbd86371 | ||
|
|
2d3666e339 | ||
|
|
47987b7785 | ||
|
|
2e1461e6dc | ||
|
|
272457740f | ||
|
|
58c721e7d2 | ||
|
|
b4baf35ed8 | ||
|
|
2001d96148 | ||
|
|
5c390341fb | ||
|
|
c8b1b162ff | ||
|
|
a55645584b | ||
|
|
f536307914 | ||
|
|
4494ffabb6 | ||
|
|
2dc59a601c | ||
|
|
4ad04e2032 | ||
|
|
f2ebe128cc | ||
|
|
f0165c1ad8 | ||
|
|
898f80cb30 | ||
|
|
5d7de0858c | ||
|
|
c0b88fe736 | ||
|
|
fa43248e30 | ||
|
|
cb3da25bc8 | ||
|
|
a40058bb0b | ||
|
|
6ab3bbe7bd | ||
|
|
9e73c82eb3 | ||
|
|
6b3b1b6cbc | ||
|
|
b3b433f84b | ||
|
|
7f16a53309 | ||
|
|
2471bda211 | ||
|
|
cd49971535 | ||
|
|
0013f8989b | ||
|
|
de8c80597f | ||
|
|
9dcc55ea1b | ||
|
|
8255390131 | ||
|
|
438a9684ee | ||
|
|
a6000f22a2 | ||
|
|
2ec5eeb442 | ||
|
|
d319476eb6 | ||
|
|
93d52bc86c | ||
|
|
bda5c2c915 | ||
|
|
b838fe2e74 | ||
|
|
604b9be4a0 |
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
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
|
||||
|
||||
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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
@@ -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
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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.
|
||||
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal 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
|
||||
10
.github/ISSUE_TEMPLATE/question-.md
vendored
@@ -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
@@ -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
|
||||
10
.github/dependabot.yml
vendored
@@ -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
@@ -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. -->
|
||||
72
.github/workflows/docker.yml
vendored
@@ -1,41 +1,57 @@
|
||||
name: Release 3X-UI for Docker
|
||||
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
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- 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=pep440,pattern={{version}}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
|
||||
- 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
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
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 }}
|
||||
204
.github/workflows/release.yml
vendored
@@ -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-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,43 +85,43 @@ jobs:
|
||||
cd x-ui/bin
|
||||
|
||||
# Download dependencies
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.23/"
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.9.11/"
|
||||
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 ../..
|
||||
|
||||
@@ -134,9 +136,89 @@ jobs:
|
||||
|
||||
- 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.9.11/"
|
||||
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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.23/Xray-linux-${ARCH}.zip"
|
||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.9.11/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 ../../
|
||||
@@ -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,7 @@ RUN chmod +x \
|
||||
/app/x-ui \
|
||||
/usr/bin/x-ui
|
||||
|
||||
ENV XUI_ENABLE_FAIL2BAN="true"
|
||||
VOLUME [ "/etc/x-ui" ]
|
||||
CMD [ "./x-ui" ]
|
||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||
|
||||
56
README.ar_EG.md
Normal 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>
|
||||
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
||||
[](#)
|
||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
**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:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
||||
<img src="./media/buymeacoffe.png" alt="Image">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## النجوم عبر الزمن
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
503
README.es_ES.md
@@ -1,18 +1,45 @@
|
||||
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
|
||||
[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"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||
<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>
|
||||
|
||||
**Un Panel Web Avanzado • Construido sobre Xray Core**
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
||||
[](#)
|
||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
**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.
|
||||
|
||||
> **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
|
||||
> [!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.
|
||||
|
||||
**Si este proyecto te es útil, podrías considerar darle una**:star2:
|
||||
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.
|
||||
|
||||
## Inicio Rápido
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
Para documentación completa, visita la [Wiki del proyecto](https://github.com/MHSanaei/3x-ui/wiki).
|
||||
|
||||
## 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 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._
|
||||
|
||||
## Apoyar el Proyecto
|
||||
|
||||
**Si este proyecto te es útil, puedes darle una**:star2:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
||||
@@ -21,459 +48,9 @@
|
||||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## Instalar y Actualizar
|
||||
## Estrellas a lo Largo del Tiempo
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
## Instalar una Versión Personalizada
|
||||
|
||||
Para instalar la versión deseada, agrega la versión al final del comando de instalación. Por ejemplo, ver `v2.3.12`:
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.12
|
||||
```
|
||||
|
||||
## 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:
|
||||

|
||||
|
||||
- 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".
|
||||

|
||||
|
||||
- Inicia el bot que acabas de crear. Puedes encontrar el enlace a tu bot aquí.
|
||||

|
||||
|
||||
- Ingresa a tu panel y configura los ajustes del bot de Telegram como se muestra a continuación:
|
||||

|
||||
|
||||
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.
|
||||

|
||||
|
||||
</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
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 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._
|
||||
|
||||
## Estrellas a lo largo del tiempo
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
|
||||
56
README.fa_IR.md
Normal 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>
|
||||
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
||||
[](#)
|
||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
**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: بدهید
|
||||
|
||||
<p align="left">
|
||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
||||
<img src="./media/buymeacoffe.png" alt="Image">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## ستارهها در طول زمان
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
553
README.md
@@ -1,16 +1,43 @@
|
||||
[English](/README.md) | [Chinese](/README.zh.md) | [Español](/README.es_ES.md)
|
||||
[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"><a href="#"><img src="./media/3X-UI.png" alt="Image"></a></p>
|
||||
<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>
|
||||
|
||||
**An Advanced Web Panel • Built on Xray Core**
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
||||
[](#)
|
||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
**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.
|
||||
|
||||
> **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
|
||||
> [!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.
|
||||
|
||||
As an enhanced fork of the original X-UI project, 3X-UI provides improved stability, broader protocol support, and additional features.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
For full documentation, please visit the [project Wiki](https://github.com/MHSanaei/3x-ui/wiki).
|
||||
|
||||
## A Special Thanks to
|
||||
|
||||
- [alireza0](https://github.com/alireza0/)
|
||||
|
||||
## 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._
|
||||
- [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:
|
||||
|
||||
@@ -21,513 +48,9 @@
|
||||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## Install & Upgrade
|
||||
|
||||
```
|
||||
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.12`:
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.12
|
||||
```
|
||||
|
||||
## SSL Certificate
|
||||
|
||||
<details>
|
||||
<summary>Click for SSL Certificate details</summary>
|
||||
|
||||
### ACME
|
||||
|
||||
To manage SSL certificates using ACME:
|
||||
|
||||
1. Ensure your domain is correctly resolved to the server.
|
||||
2. Run the `x-ui` command in the terminal, then choose `SSL Certificate Management`.
|
||||
3. You will be presented with the following options:
|
||||
|
||||
- **Get SSL:** Obtain SSL certificates.
|
||||
- **Revoke:** Revoke existing SSL certificates.
|
||||
- **Force Renew:** Force renewal of SSL certificates.
|
||||
|
||||
### Certbot
|
||||
|
||||
To install and use Certbot:
|
||||
|
||||
```sh
|
||||
apt-get install certbot -y
|
||||
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||
certbot renew --dry-run
|
||||
```
|
||||
|
||||
### Cloudflare
|
||||
|
||||
The management script includes 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 must be resolved to the current server through Cloudflare
|
||||
|
||||
**How to get the Cloudflare Global API Key:**
|
||||
|
||||
1. Run the `x-ui` command in the terminal, then choose `Cloudflare SSL Certificate`.
|
||||
2. Visit the link: [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens).
|
||||
3. Click on "View Global API Key" (see the screenshot below):
|
||||

|
||||
4. You may need to re-authenticate your account. After that, the API Key will be shown (see the screenshot below):
|
||||

|
||||
|
||||
When using, just enter your `domain name`, `email`, and `API KEY`. The diagram is as follows:
|
||||

|
||||
|
||||
|
||||
</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
|
||||
```
|
||||
|
||||
4. **Update to the Latest Version:**
|
||||
|
||||
```sh
|
||||
cd 3x-ui
|
||||
docker compose down
|
||||
docker compose pull 3x-ui
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
5. **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 Panel Settings
|
||||
|
||||
<details>
|
||||
<summary>Click for default settings details</summary>
|
||||
|
||||
### Username & Password & webbasepath:
|
||||
|
||||
These will be generated randomly if you skip modifying them.
|
||||
|
||||
- **Port:** the default port for panel is `2053`
|
||||
|
||||
### Database Management:
|
||||
|
||||
You can conveniently perform database Backups and Restores directly from the panel.
|
||||
|
||||
- **Database Path:**
|
||||
- `/etc/x-ui/x-ui.db`
|
||||
|
||||
|
||||
### Web Base Path
|
||||
|
||||
1. **Reset Web Base Path:**
|
||||
- Open your terminal.
|
||||
- Run the `x-ui` command.
|
||||
- Select the option to `Reset Web Base Path`.
|
||||
|
||||
2. **Generate or Customize Path:**
|
||||
- The path will be randomly generated, or you can enter a custom path.
|
||||
|
||||
3. **View Current Settings:**
|
||||
- To view your current settings, use the `x-ui settings` command in the terminal or `View Current Settings` in `x-ui`
|
||||
|
||||
### Security Recommendation:
|
||||
- For enhanced security, use a long, random word in your URL structure.
|
||||
|
||||
**Examples:**
|
||||
- `http://ip:port/*webbasepath*/panel`
|
||||
- `http://domain:port/*webbasepath*/panel`
|
||||
|
||||
</details>
|
||||
|
||||
## WARP Configuration
|
||||
|
||||
<details>
|
||||
<summary>Click for WARP configuration details</summary>
|
||||
|
||||
#### Usage
|
||||
|
||||
**For versions `v2.1.0` and later:**
|
||||
|
||||
WARP is built-in, and no additional installation is required. Simply turn on the necessary configuration in the panel.
|
||||
|
||||
**For versions before `v2.1.0`:**
|
||||
|
||||
1. Run the `x-ui` command in the terminal, then choose `WARP Management`.
|
||||
2. You will see the following options:
|
||||
|
||||
- **Account Type (free, plus, team):** Choose the appropriate account type.
|
||||
- **Enable/Disable WireProxy:** Toggle WireProxy on or off.
|
||||
- **Uninstall WARP:** Remove the WARP application.
|
||||
|
||||
3. Configure the settings as needed in the panel.
|
||||
|
||||
</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`:**
|
||||
- The IP limit is built-in to the panel
|
||||
|
||||
**For versions `v1.7.0` and newer:**
|
||||
|
||||
To enable the IP Limit functionality, you need to install `fail2ban` and its required files by following these steps:
|
||||
|
||||
1. Run the `x-ui` command in the terminal, then choose `IP Limit Management`.
|
||||
2. You will see the following options:
|
||||
|
||||
- **Change Ban Duration:** Adjust the duration of bans.
|
||||
- **Unban Everyone:** Lift all current bans.
|
||||
- **Check Logs:** Review the logs.
|
||||
- **Fail2ban Status:** Check the status of `fail2ban`.
|
||||
- **Restart Fail2ban:** Restart the `fail2ban` service.
|
||||
- **Uninstall Fail2ban:** Uninstall Fail2ban with configuration.
|
||||
|
||||
3. Add a path for the access log on the panel by setting `Xray Configs/log/Access log` to `./access.log` then save and restart xray.
|
||||
|
||||
- **For versions before `v2.1.3`:**
|
||||
- You need to set the access log path manually in your Xray configuration:
|
||||
|
||||
```sh
|
||||
"log": {
|
||||
"access": "./access.log",
|
||||
"dnsLog": false,
|
||||
"loglevel": "warning"
|
||||
},
|
||||
```
|
||||
|
||||
- **For versions `v2.1.3` and newer:**
|
||||
- There is an option for configuring `access.log` directly from the panel.
|
||||
|
||||
</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:
|
||||

|
||||
|
||||
- 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".
|
||||

|
||||
|
||||
- Start the bot you've just created. You can find the link to your bot here.
|
||||

|
||||
|
||||
- Enter your panel and config Telegram bot settings like below:
|
||||

|
||||
|
||||
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.
|
||||

|
||||
|
||||
</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` | `"/getClientTrafficsById/:id"` | Get client's traffic By ID |
|
||||
| `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
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## A Special Thanks to
|
||||
|
||||
- [alireza0](https://github.com/alireza0/)
|
||||
|
||||
## 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._
|
||||
|
||||
## Stargazers over Time
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
|
||||
56
README.ru_RU.md
Normal 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>
|
||||
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
||||
[](#)
|
||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
**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:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
||||
<img src="./media/buymeacoffe.png" alt="Image">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## Звезды с течением времени
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
478
README.zh.md
@@ -1,478 +0,0 @@
|
||||
[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://github.com/MHSanaei/3x-ui/releases)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
> **Disclaimer:** 此项目仅供个人学习交流,请不要用于非法目的,请不要在生产环境中使用。
|
||||
|
||||
**如果此项目对你有用,请给一个**:star2:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
||||
<img src="./media/buymeacoffe.png" alt="Image">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## 安装 & 升级
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
## 安装指定版本
|
||||
|
||||
要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.3.12`:
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.12
|
||||
```
|
||||
|
||||
## 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 阈值通知
|
||||
- 提前报告的过期时间和流量阈值
|
||||
- 如果将客户的电报用户名添加到用户的配置中,则支持客户端报告菜单
|
||||
- 支持使用UUID(VMESS/VLESS)或密码(TROJAN)搜索报文流量报告 - 匿名
|
||||
- 基于菜单的机器人
|
||||
- 通过电子邮件搜索客户端(仅限管理员)
|
||||
- 检查所有入库
|
||||
- 检查服务器状态
|
||||
- 检查耗尽的用户
|
||||
- 根据请求和定期报告接收备份
|
||||
- 多语言机器人
|
||||
|
||||
### 注册 Telegram bot
|
||||
|
||||
- 与 [Botfather](https://t.me/BotFather) 对话:
|
||||

|
||||
|
||||
- 使用 /newbot 创建新机器人:你需要提供机器人名称以及用户名,注意名称中末尾要包含“bot”
|
||||

|
||||
|
||||
- 启动您刚刚创建的机器人。可以在此处找到机器人的链接。
|
||||

|
||||
|
||||
- 输入您的面板并配置 Telegram 机器人设置,如下所示:
|
||||

|
||||
|
||||
在输入字段编号 3 中输入机器人令牌。
|
||||
在输入字段编号 4 中输入用户 ID。具有此 id 的 Telegram 帐户将是机器人管理员。 (您可以输入多个,只需将它们用“ ,”分开即可)
|
||||
|
||||
- 如何获取TG ID? 使用 [bot](https://t.me/useridinfobot), 启动机器人,它会给你 Telegram 用户 ID。
|
||||

|
||||
|
||||
</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>
|
||||
|
||||
## 预览
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 特别感谢
|
||||
|
||||
- [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趋势
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
56
README.zh_CN.md
Normal 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>
|
||||
|
||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
||||
[](#)
|
||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
**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:
|
||||
|
||||
<p align="left">
|
||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
||||
<img src="./media/buymeacoffe.png" alt="Image">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## 随时间变化的星标数
|
||||
|
||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||
@@ -3,7 +3,10 @@ package config
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -54,12 +57,32 @@ func GetBinFolderPath() string {
|
||||
return binFolderPath
|
||||
}
|
||||
|
||||
func getBaseDir() string {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "."
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func GetDBFolderPath() string {
|
||||
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||
if dbFolderPath == "" {
|
||||
dbFolderPath = "/etc/x-ui"
|
||||
if dbFolderPath != "" {
|
||||
return dbFolderPath
|
||||
}
|
||||
return dbFolderPath
|
||||
if runtime.GOOS == "windows" {
|
||||
return getBaseDir()
|
||||
}
|
||||
return "/etc/x-ui"
|
||||
}
|
||||
|
||||
func GetDBPath() string {
|
||||
@@ -68,8 +91,54 @@ func GetDBPath() string {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.3.12
|
||||
2.8.0
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database/model"
|
||||
"x-ui/util/crypto"
|
||||
"x-ui/xray"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
@@ -22,17 +24,17 @@ var db *gorm.DB
|
||||
const (
|
||||
defaultUsername = "admin"
|
||||
defaultPassword = "admin"
|
||||
defaultSecret = ""
|
||||
)
|
||||
|
||||
func initModels() error {
|
||||
models := []interface{}{
|
||||
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 {
|
||||
@@ -50,16 +52,61 @@ func initUser() error {
|
||||
return err
|
||||
}
|
||||
if empty {
|
||||
hashedPassword, err := crypto.HashPasswordAsBcrypt(defaultPassword)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error hashing default password: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
user := &model.User{
|
||||
Username: defaultUsername,
|
||||
Password: defaultPassword,
|
||||
LoginSecret: defaultSecret,
|
||||
Username: defaultUsername,
|
||||
Password: hashedPassword,
|
||||
}
|
||||
return db.Create(user).Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 isTableEmpty(tableName string) (bool, error) {
|
||||
var count int64
|
||||
err := db.Table(tableName).Count(&count).Error
|
||||
@@ -92,11 +139,13 @@ func InitDB(dbPath string) error {
|
||||
if err := initModels(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isUsersEmpty, err := isTableEmpty("users")
|
||||
|
||||
if err := initUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return runSeeders(isUsersEmpty)
|
||||
}
|
||||
|
||||
func CloseDB() error {
|
||||
|
||||
@@ -10,21 +10,20 @@ import (
|
||||
type Protocol string
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type Inbound struct {
|
||||
@@ -33,6 +32,7 @@ type Inbound struct {
|
||||
Up int64 `json:"up" form:"up"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
Total int64 `json:"total" form:"total"`
|
||||
AllTime int64 `json:"allTime" form:"allTime" gorm:"default:0"`
|
||||
Remark string `json:"remark" form:"remark"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
@@ -62,6 +62,11 @@ type InboundClientIps struct {
|
||||
Ips string `json:"ips" form:"ips"`
|
||||
}
|
||||
|
||||
type HistoryOfSeeders struct {
|
||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
SeederName string `json:"seederName"`
|
||||
}
|
||||
|
||||
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
||||
listen := i.Listen
|
||||
if listen != "" {
|
||||
@@ -86,6 +91,7 @@ type Setting struct {
|
||||
|
||||
type Client struct {
|
||||
ID string `json:"id"`
|
||||
Security string `json:"security"`
|
||||
Password string `json:"password"`
|
||||
Flow string `json:"flow"`
|
||||
Email string `json:"email"`
|
||||
@@ -95,5 +101,15 @@ type Client struct {
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
TgID int64 `json:"tgId" form:"tgId"`
|
||||
SubID string `json:"subId" form:"subId"`
|
||||
Comment string `json:"comment" form:"comment"`
|
||||
Reset int `json:"reset" form:"reset"`
|
||||
CreatedAt int64 `json:"created_at,omitempty"`
|
||||
UpdatedAt int64 `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
type VLESSSettings struct {
|
||||
Clients []Client `json:"clients"`
|
||||
Decryption string `json:"decryption"`
|
||||
Encryption string `json:"encryption"`
|
||||
Fallbacks []any `json:"fallbacks"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
132
go.mod
@@ -1,102 +1,102 @@
|
||||
module x-ui
|
||||
|
||||
go 1.22.5
|
||||
go 1.25.1
|
||||
|
||||
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.31.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/gin-contrib/gzip v1.2.3
|
||||
github.com/gin-contrib/sessions v1.0.4
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
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.7
|
||||
github.com/valyala/fasthttp v1.55.0
|
||||
github.com/xtls/xray-core v1.8.23
|
||||
github.com/shirou/gopsutil/v4 v4.25.8
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/valyala/fasthttp v1.65.0
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
github.com/xtls/xray-core v1.250911.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.16.0
|
||||
google.golang.org/grpc v1.65.0
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/gorm v1.25.11
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/text v0.29.0
|
||||
google.golang.org/grpc v1.75.1
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.30.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.12.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.0 // 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.2 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // 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.8.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // 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.22.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-20240727154555-813a5fbdbec8 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.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.3.0 // 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.8 // 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-20240513124658-fba389f38bae // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // 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.1 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // 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.4.0 // indirect
|
||||
github.com/quic-go/quic-go v0.45.2 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/refraction-networking/utls v1.8.0 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/sagernet/sing v0.4.2 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/sagernet/sing v0.7.7 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // 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-20240712055506-48f0b2d5ed6d // indirect
|
||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.9.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
golang.org/x/arch v0.21.0 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
golang.org/x/tools v0.36.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-20240805194559-2c9e96a0b5d4 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // 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
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
||||
lukechampine.com/blake3 v1.4.1 // indirect
|
||||
)
|
||||
|
||||
475
go.sum
@@ -1,66 +1,42 @@
|
||||
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.12.0 h1:YGPgxF9xzaCNvd/ZKdQ28yRovhfMFZQjuk6fKBzZ3ls=
|
||||
github.com/bytedance/sonic v1.12.0/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||
github.com/bytedance/sonic/loader v0.2.0/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/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
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.2 h1:ckJCCdV7hWkkrMeId3WfEhz+4Gyyf6QPwxi/RHIMZ6I=
|
||||
github.com/fasthttp/router v1.5.2/go.mod h1:C8EY53ozOwpONyevc/V7Gr8pqnEjwnkFFqPo1alAGs0=
|
||||
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.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
||||
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.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U=
|
||||
github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c=
|
||||
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.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
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=
|
||||
@@ -70,341 +46,212 @@ 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.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||
github.com/go-playground/validator/v10 v10.22.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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
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/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-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/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.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
|
||||
github.com/gorilla/sessions v1.3.0/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/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.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/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-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/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-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
|
||||
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/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.31.0 h1:vsN+JCNkh7Z9vfL/2/AHZ2xBsRk2GCMj3zydjCxkgIc=
|
||||
github.com/mymmrac/telego v0.31.0/go.mod h1:MuqgVf2xXnIOWZs0prvsp3f4Yss80kCSjVEj4CRl7Ig=
|
||||
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.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0=
|
||||
github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA=
|
||||
github.com/onsi/gomega v1.34.0 h1:eSSPsPNp6ZpsG8X1OVmOTxig+CblTc4AxpPBykhe2Os=
|
||||
github.com/onsi/gomega v1.34.0/go.mod h1:MIKI8c+f+QLWk+hxbePD4i0LMJSExPaZOVfkoex4cAo=
|
||||
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-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/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/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.45.2 h1:DfqBmqjb4ExSdxRIb/+qXhPC+7k6+DUNZha4oeiC9fY=
|
||||
github.com/quic-go/quic-go v0.45.2/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
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.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE=
|
||||
github.com/refraction-networking/utls v1.8.0/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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.4.2 h1:jzGNJdZVRI0xlAfFugsIQUPvyB9SuWvbJK7zQCXc4QM=
|
||||
github.com/sagernet/sing v0.4.2/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
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.7 h1:o46FzVZS+wKbBMEkMEdEHoVZxyM9jvfRpKXc7pEgS/c=
|
||||
github.com/sagernet/sing v0.7.7/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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil/v4 v4.24.7 h1:V9UGTK4gQ8HvcnPKf6Zt3XHyQq/peaekfxpJ2HSocJk=
|
||||
github.com/shirou/gopsutil/v4 v4.24.7/go.mod h1:0uW/073rP7FYLOkvxolUQM5rMOLTNmRXnFKafpb71rw=
|
||||
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/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970=
|
||||
github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI=
|
||||
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.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
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.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
|
||||
github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4=
|
||||
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-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||
github.com/xtls/xray-core v1.8.23 h1:A8Wr50ildMYLpaNu3EiK+Stg/tps6i0h7z5Hr4f9H2k=
|
||||
github.com/xtls/xray-core v1.8.23/go.mod h1:0CwyMPNA5Cs+ukPXHbYQGgne/ug0PuXOSVqBu7zyXOc=
|
||||
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-20250904214705-431b6ff8c67c h1:LHLhQY3mKXSpTcQAkjFR4/6ar3rXjQryNeM7khK3AHU=
|
||||
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
||||
github.com/xtls/xray-core v1.250911.0 h1:KMN8zVurAjHFixiUoFV/jwmzYohf27dQRntjV+8LQno=
|
||||
github.com/xtls/xray-core v1.250911.0/go.mod h1:LkqA/BFVtPS2e5fRzg/bkYas9nQu4Uztlx+/fjlLM9k=
|
||||
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.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k=
|
||||
golang.org/x/arch v0.9.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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
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.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.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.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw=
|
||||
golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
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.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.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.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.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.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
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-20240805194559-2c9e96a0b5d4 h1:OsSGQeIIsyOEOimVxLEIL4rwGcnrjOydQaiA2bOnZUM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240805194559-2c9e96a0b5d4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
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.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
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-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
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.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
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=
|
||||
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.30.5 h1:dvEfYwxL+i+xgCNSGGBT1lDjCzfELK8fHZxL3Ee9X0s=
|
||||
gorm.io/gorm v1.30.5/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
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=
|
||||
|
||||
243
install.sh
@@ -2,6 +2,7 @@
|
||||
|
||||
red='\033[0;31m'
|
||||
green='\033[0;32m'
|
||||
blue='\033[0;34m'
|
||||
yellow='\033[0;33m'
|
||||
plain='\033[0m'
|
||||
|
||||
@@ -36,77 +37,17 @@ 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)
|
||||
@@ -116,7 +57,7 @@ install_base() {
|
||||
zypper refresh && zypper -q install -y wget curl tar timezone
|
||||
;;
|
||||
*)
|
||||
apt-get update && apt install -y -q wget curl tar tzdata
|
||||
apt-get update && apt-get install -y -q wget curl tar tzdata
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -127,119 +68,163 @@ gen_random_string() {
|
||||
echo "$random_string"
|
||||
}
|
||||
|
||||
# This function will be called when user installed x-ui out of security
|
||||
config_after_install() {
|
||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||
read -p "Would you like to customize the panel settings? (If not, random settings will be applied) [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=$(gen_random_string 10)
|
||||
/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 "${yellow}If you forgot your login info, you can type "x-ui settings" to check after installation${plain}"
|
||||
else
|
||||
echo -e "${yellow}This is your upgrade, will keep old settings. If you forgot your login info, you can type "x-ui settings" 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}"
|
||||
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 "${red}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}"
|
||||
exit 1
|
||||
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 -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 -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 -O /usr/bin/x-ui-temp https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
||||
|
||||
# Stop x-ui service and remove old resources
|
||||
if [[ -e /usr/local/x-ui/ ]]; then
|
||||
systemctl stop x-ui
|
||||
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
|
||||
|
||||
cp -f x-ui.service /etc/systemd/system/
|
||||
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..."
|
||||
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 "SUBCOMMANDS:"
|
||||
echo -e "x-ui - Admin Management Script"
|
||||
echo -e "x-ui start - Start"
|
||||
echo -e "x-ui stop - Stop"
|
||||
echo -e "x-ui restart - Restart"
|
||||
echo -e "x-ui status - Current Status"
|
||||
echo -e "x-ui settings - Current Settings"
|
||||
echo -e "x-ui enable - Enable Autostart on OS Startup"
|
||||
echo -e "x-ui disable - Disable Autostart on OS Startup"
|
||||
echo -e "x-ui log - Check logs"
|
||||
echo -e "x-ui banlog - Check Fail2ban ban logs"
|
||||
echo -e "x-ui update - Update"
|
||||
echo -e "x-ui custom - custom version"
|
||||
echo -e "x-ui install - Install"
|
||||
echo -e "x-ui uninstall - Uninstall"
|
||||
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}"
|
||||
|
||||
@@ -47,52 +47,52 @@ func InitLogger(level logging.Level) {
|
||||
logger = newLogger
|
||||
}
|
||||
|
||||
func Debug(args ...interface{}) {
|
||||
func Debug(args ...any) {
|
||||
logger.Debug(args...)
|
||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
func Debugf(format string, args ...any) {
|
||||
logger.Debugf(format, args...)
|
||||
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
func Info(args ...any) {
|
||||
logger.Info(args...)
|
||||
addToBuffer("INFO", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
func Infof(format string, args ...any) {
|
||||
logger.Infof(format, args...)
|
||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Notice(args ...interface{}) {
|
||||
func Notice(args ...any) {
|
||||
logger.Notice(args...)
|
||||
addToBuffer("NOTICE", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Noticef(format string, args ...interface{}) {
|
||||
func Noticef(format string, args ...any) {
|
||||
logger.Noticef(format, args...)
|
||||
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Warning(args ...interface{}) {
|
||||
func Warning(args ...any) {
|
||||
logger.Warning(args...)
|
||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
func Warningf(format string, args ...any) {
|
||||
logger.Warningf(format, args...)
|
||||
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
func Error(args ...any) {
|
||||
logger.Error(args...)
|
||||
addToBuffer("ERROR", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
func Errorf(format string, args ...any) {
|
||||
logger.Errorf(format, args...)
|
||||
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
143
main.go
@@ -13,10 +13,12 @@ import (
|
||||
"x-ui/database"
|
||||
"x-ui/logger"
|
||||
"x-ui/sub"
|
||||
"x-ui/util/crypto"
|
||||
"x-ui/web"
|
||||
"x-ui/web/global"
|
||||
"x-ui/web/service"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
@@ -38,6 +40,8 @@ func runWebServer() {
|
||||
log.Fatalf("Unknown log level: %v", config.GetLogLevel())
|
||||
}
|
||||
|
||||
godotenv.Load()
|
||||
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing database: %v", err)
|
||||
@@ -73,11 +77,11 @@ func runWebServer() {
|
||||
|
||||
err := server.Stop()
|
||||
if err != nil {
|
||||
logger.Warning("Error stopping web server:", err)
|
||||
logger.Debug("Error stopping web server:", err)
|
||||
}
|
||||
err = subServer.Stop()
|
||||
if err != nil {
|
||||
logger.Warning("Error stopping sub server:", err)
|
||||
logger.Debug("Error stopping sub server:", err)
|
||||
}
|
||||
|
||||
server = web.NewServer()
|
||||
@@ -136,27 +140,39 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +232,7 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
|
||||
}
|
||||
}
|
||||
|
||||
func updateSetting(port int, username string, password string, webBasePath string) {
|
||||
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
fmt.Println("Database initialization failed:", err)
|
||||
@@ -252,6 +268,26 @@ func updateSetting(port int, username string, password string, webBasePath strin
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateCert(publicKey string, privateKey string) {
|
||||
@@ -281,6 +317,37 @@ func updateCert(publicKey string, privateKey string) {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateDb() {
|
||||
inboundService := service.InboundService{}
|
||||
|
||||
@@ -293,36 +360,6 @@ 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.")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
runWebServer()
|
||||
@@ -339,6 +376,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,14 +386,18 @@ func main() {
|
||||
var tgbotRuntime string
|
||||
var reset bool
|
||||
var show bool
|
||||
var remove_secret bool
|
||||
var getCert bool
|
||||
var resetTwoFactor bool
|
||||
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
||||
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
||||
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "Remove secret key")
|
||||
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")
|
||||
@@ -397,17 +440,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 +468,6 @@ func main() {
|
||||
} else {
|
||||
updateCert(webCertFile, webKeyFile)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Println("Invalid subcommands")
|
||||
fmt.Println()
|
||||
|
||||
BIN
media/01-overview-dark.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
media/01-overview-light.png
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
media/02-inbounds-dark.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
media/02-inbounds-light.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
media/03-add-inbound-dark.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
media/03-add-inbound-light.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
media/04-add-client-dark.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
media/04-add-client-light.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
media/05-settings-dark.png
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
media/05-settings-light.png
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
media/06-configs-dark.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
media/06-configs-light.png
Normal file
|
After Width: | Height: | Size: 206 KiB |
BIN
media/07-bot-dark.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
media/07-bot-light.png
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
media/1.png
|
Before Width: | Height: | Size: 59 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 91 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
BIN
media/3x-ui-light.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 26 KiB |
BIN
media/5.png
|
Before Width: | Height: | Size: 71 KiB |
BIN
media/6.png
|
Before Width: | Height: | Size: 42 KiB |
BIN
media/7.png
|
Before Width: | Height: | Size: 60 KiB |
@@ -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": []
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
104
sub/sub.go
@@ -3,14 +3,19 @@ package sub
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
webpkg "x-ui/web"
|
||||
"x-ui/web/locale"
|
||||
"x-ui/web/middleware"
|
||||
"x-ui/web/network"
|
||||
"x-ui/web/service"
|
||||
@@ -18,6 +23,21 @@ import (
|
||||
"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/subscription.html",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
engine.SetHTMLTemplate(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
httpServer *http.Server
|
||||
listener net.Listener
|
||||
@@ -38,13 +58,10 @@ func NewServer() *Server {
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -57,6 +74,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
||||
}
|
||||
|
||||
// Provide base_path in context for templates
|
||||
engine.Use(func(c *gin.Context) {
|
||||
c.Set("base_path", "/")
|
||||
})
|
||||
|
||||
LinksPath, err := s.settingService.GetSubPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -92,6 +114,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 +129,74 @@ 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
|
||||
if _, err := os.Stat("web/assets"); err == nil {
|
||||
engine.StaticFS("/assets", http.FS(os.DirFS("web/assets")))
|
||||
} else {
|
||||
if subFS, err := fs.Sub(webpkg.EmbeddedAssets(), "assets"); err == nil {
|
||||
engine.StaticFS("/assets", http.FS(subFS))
|
||||
} else {
|
||||
logger.Error("sub: failed to mount embedded assets:", err)
|
||||
}
|
||||
}
|
||||
|
||||
g := engine.Group("/")
|
||||
|
||||
s.sub = NewSUBController(
|
||||
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||
SubJsonFragment, SubJsonMux, SubJsonRules)
|
||||
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", "subscription.html")
|
||||
if _, err := os.Stat(page); err == nil {
|
||||
files = append(files, page)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start() (err error) {
|
||||
// This is an anonymous function, no function name
|
||||
defer func() {
|
||||
|
||||
@@ -2,12 +2,14 @@ package sub
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"strings"
|
||||
"x-ui/config"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SUBController struct {
|
||||
subTitle string
|
||||
subPath string
|
||||
subJsonPath string
|
||||
subEncrypt bool
|
||||
@@ -26,18 +28,21 @@ func NewSUBController(
|
||||
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,
|
||||
subEncrypt: encrypt,
|
||||
updateInterval: update,
|
||||
|
||||
subService: sub,
|
||||
subJsonService: NewSubJsonService(jsonFragment, jsonMux, jsonRules, sub),
|
||||
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
|
||||
}
|
||||
a.initRouter(g)
|
||||
return a
|
||||
@@ -48,24 +53,13 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||
gJson := g.Group(a.subJsonPath)
|
||||
|
||||
gLink.GET(":subid", a.subs)
|
||||
|
||||
gJson.GET(":subid", a.subJsons)
|
||||
}
|
||||
|
||||
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, header, lastOnline, err := a.subService.GetSubs(subId, host)
|
||||
if err != nil || len(subs) == 0 {
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
@@ -74,10 +68,38 @@ 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)
|
||||
page := a.subService.BuildPageData(subId, hostHeader, header, lastOnline, subs, subURL, subJsonURL)
|
||||
c.HTML(200, "subscription.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)
|
||||
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
|
||||
|
||||
if a.subEncrypt {
|
||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||
@@ -89,27 +111,21 @@ func (a *SUBController) subs(c *gin.Context) {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
@@ -18,20 +18,21 @@ import (
|
||||
var defaultJson string
|
||||
|
||||
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{}
|
||||
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 +40,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,10 +53,15 @@ 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,
|
||||
}
|
||||
@@ -142,10 +148,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 +163,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,7 +171,7 @@ 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["tslSettings"] = map[string]any{}
|
||||
}
|
||||
case "none":
|
||||
if newStream["security"] != "none" {
|
||||
@@ -178,14 +184,20 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||
var newOutbounds []json_util.RawMessage
|
||||
|
||||
switch inbound.Protocol {
|
||||
case "vmess", "vless":
|
||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
|
||||
case "vmess":
|
||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client, ""))
|
||||
case "vless":
|
||||
var vlessSettings model.VLESSSettings
|
||||
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
|
||||
|
||||
newOutbounds = append(newOutbounds,
|
||||
s.genVnext(inbound, streamSettings, client, vlessSettings.Encryption))
|
||||
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 +211,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 +240,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 +263,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 {
|
||||
@@ -276,15 +290,18 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
||||
return rltyData
|
||||
}
|
||||
|
||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client, encryption string) json_util.RawMessage {
|
||||
outbound := Outbound{}
|
||||
usersData := make([]UserVnext, 1)
|
||||
|
||||
usersData[0].ID = client.ID
|
||||
usersData[0].Level = 8
|
||||
if inbound.Protocol == model.VMESS {
|
||||
usersData[0].Security = client.Security
|
||||
}
|
||||
if inbound.Protocol == model.VLESS {
|
||||
usersData[0].Flow = client.Flow
|
||||
usersData[0].Encryption = "none"
|
||||
usersData[0].Encryption = encryption
|
||||
}
|
||||
|
||||
vnextData := make([]VnextSetting, 1)
|
||||
@@ -320,7 +337,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
|
||||
@@ -348,12 +365,12 @@ 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"`
|
||||
Protocol string `json:"protocol"`
|
||||
Tag string `json:"tag"`
|
||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||
ProxySettings map[string]any `json:"proxySettings,omitempty"`
|
||||
Settings OutboundSettings `json:"settings,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundSettings struct {
|
||||
@@ -371,6 +388,7 @@ type UserVnext struct {
|
||||
Encryption string `json:"encryption,omitempty"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Security string `json:"security,omitempty"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,15 @@ package sub
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
@@ -14,8 +19,6 @@ import (
|
||||
"x-ui/util/random"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type SubService struct {
|
||||
@@ -34,19 +37,20 @@ func NewSubService(showInfo bool, remarkModel string) *SubService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
||||
func (s *SubService) GetSubs(subId string, host string) ([]string, string, int64, 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, err
|
||||
}
|
||||
|
||||
if len(inbounds) == 0 {
|
||||
return nil, "", common.NewError("No inbounds found with ", subId)
|
||||
return nil, "", 0, common.NewError("No inbounds found with ", subId)
|
||||
}
|
||||
|
||||
s.datepicker, err = s.settingService.GetDatepicker()
|
||||
@@ -73,7 +77,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +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, header, lastOnline, 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,10 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
if inbound.Protocol != model.VLESS {
|
||||
return ""
|
||||
}
|
||||
var stream map[string]interface{}
|
||||
var vlessSettings model.VLESSSettings
|
||||
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
|
||||
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
clients, _ := s.inboundService.GetClients(inbound)
|
||||
clientIndex := -1
|
||||
@@ -336,76 +338,70 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
port := inbound.Port
|
||||
streamNetwork := stream["network"].(string)
|
||||
params := make(map[string]string)
|
||||
if vlessSettings.Encryption != "" {
|
||||
params["encryption"] = vlessSettings.Encryption
|
||||
}
|
||||
params["type"] = streamNetwork
|
||||
|
||||
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 +432,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 +451,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 +464,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 +526,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 +544,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 +627,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 +646,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 +659,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 +722,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 +743,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 +830,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))
|
||||
@@ -1043,9 +968,9 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
||||
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
|
||||
@@ -1054,7 +979,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
|
||||
@@ -1064,19 +989,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)
|
||||
}
|
||||
}
|
||||
@@ -1084,3 +1009,172 @@ func searchHost(headers interface{}) string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// PageData is a view model for subscription.html
|
||||
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.
|
||||
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 URLs.
|
||||
func (s *SubService) BuildURLs(scheme, hostWithPort, subPath, subJsonPath, subId string) (subURL, subJsonURL string) {
|
||||
if strings.HasSuffix(subPath, "/") {
|
||||
subURL = scheme + "://" + hostWithPort + subPath + subId
|
||||
} else {
|
||||
subURL = scheme + "://" + hostWithPort + strings.TrimRight(subPath, "/") + "/" + subId
|
||||
}
|
||||
if strings.HasSuffix(subJsonPath, "/") {
|
||||
subJsonURL = scheme + "://" + hostWithPort + subJsonPath + subId
|
||||
} else {
|
||||
subJsonURL = scheme + "://" + hostWithPort + strings.TrimRight(subJsonPath, "/") + "/" + subId
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BuildPageData parses header and prepares the template view model.
|
||||
func (s *SubService) BuildPageData(subId, hostHeader, header string, lastOnline int64, subs []string, subURL, subJsonURL string) PageData {
|
||||
// Parse header values
|
||||
var uploadByte, downloadByte, totalByte, expire int64
|
||||
parts := strings.Split(header, ";")
|
||||
for _, p := range parts {
|
||||
kv := strings.Split(strings.TrimSpace(p), "=")
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(kv[0]))
|
||||
val := strings.TrimSpace(kv[1])
|
||||
switch key {
|
||||
case "upload":
|
||||
if v, err := parseInt64(val); err == nil {
|
||||
uploadByte = v
|
||||
}
|
||||
case "download":
|
||||
if v, err := parseInt64(val); err == nil {
|
||||
downloadByte = v
|
||||
}
|
||||
case "total":
|
||||
if v, err := parseInt64(val); err == nil {
|
||||
totalByte = v
|
||||
}
|
||||
case "expire":
|
||||
if v, err := parseInt64(val); err == nil {
|
||||
expire = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
download := common.FormatTraffic(downloadByte)
|
||||
upload := common.FormatTraffic(uploadByte)
|
||||
total := "∞"
|
||||
used := common.FormatTraffic(uploadByte + downloadByte)
|
||||
remained := ""
|
||||
if totalByte > 0 {
|
||||
total = common.FormatTraffic(totalByte)
|
||||
left := totalByte - (uploadByte + downloadByte)
|
||||
if left < 0 {
|
||||
left = 0
|
||||
}
|
||||
remained = common.FormatTraffic(left)
|
||||
}
|
||||
|
||||
datepicker := s.datepicker
|
||||
if datepicker == "" {
|
||||
datepicker = "gregorian"
|
||||
}
|
||||
|
||||
return PageData{
|
||||
Host: hostHeader,
|
||||
BasePath: "/",
|
||||
SId: subId,
|
||||
Download: download,
|
||||
Upload: upload,
|
||||
Total: total,
|
||||
Used: used,
|
||||
Remained: remained,
|
||||
Expire: expire,
|
||||
LastOnline: lastOnline,
|
||||
Datepicker: datepicker,
|
||||
DownloadByte: downloadByte,
|
||||
UploadByte: uploadByte,
|
||||
TotalByte: totalByte,
|
||||
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
|
||||
}
|
||||
|
||||
func parseInt64(s string) (int64, error) {
|
||||
// handle potential quotes
|
||||
s = strings.Trim(s, "\"'")
|
||||
n, err := strconv.ParseInt(s, 10, 64)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -7,17 +7,17 @@ import (
|
||||
"x-ui/logger"
|
||||
)
|
||||
|
||||
func NewErrorf(format string, a ...interface{}) error {
|
||||
func NewErrorf(format string, a ...any) error {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func NewError(a ...interface{}) error {
|
||||
func NewError(a ...any) error {
|
||||
msg := fmt.Sprintln(a...)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func Recover(msg string) interface{} {
|
||||
func Recover(msg string) any {
|
||||
panicErr := recover()
|
||||
if panicErr != nil {
|
||||
if msg != "" {
|
||||
|
||||
@@ -4,18 +4,14 @@ 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))
|
||||
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])
|
||||
}
|
||||
|
||||
15
util/crypto/crypto.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func HashPasswordAsBcrypt(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(hash), err
|
||||
}
|
||||
|
||||
func CheckPasswordHash(hash, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
@@ -44,11 +44,11 @@ func getLinesNum(filename string) (int, error) {
|
||||
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 +59,23 @@ 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
3
web/assets/axios/axios.min.js
vendored
1
web/assets/base64/base64.min.js
vendored
@@ -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}});
|
||||
7
web/assets/clipboard/clipboard.min.js
vendored
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
2
web/assets/css/custom.min.css
vendored
@@ -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}}
|
||||
@@ -1,88 +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: '🇺🇦',
|
||||
},
|
||||
{
|
||||
name: 'Türkçe',
|
||||
value: 'tr-TR',
|
||||
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;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ class DBInbound {
|
||||
this.up = 0;
|
||||
this.down = 0;
|
||||
this.total = 0;
|
||||
this.allTime = 0;
|
||||
this.remark = "";
|
||||
this.enable = true;
|
||||
this.expiryTime = 0;
|
||||
@@ -25,11 +26,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 +49,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 +141,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);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@ const UTLS_FINGERPRINT = {
|
||||
UTLS_QQ: "qq",
|
||||
UTLS_RANDOM: "random",
|
||||
UTLS_RANDOMIZED: "randomized",
|
||||
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
|
||||
UTLS_UNSAFE: "unsafe",
|
||||
};
|
||||
|
||||
const ALPN_OPTION = {
|
||||
@@ -69,6 +71,31 @@ const WireguardDomainStrategy = [
|
||||
"ForceIPv6v4"
|
||||
];
|
||||
|
||||
const USERS_SECURITY = {
|
||||
AES_128_GCM: "aes-128-gcm",
|
||||
CHACHA20_POLY1305: "chacha20-poly1305",
|
||||
AUTO: "auto",
|
||||
NONE: "none",
|
||||
ZERO: "zero",
|
||||
};
|
||||
|
||||
const MODE_OPTION = {
|
||||
AUTO: "auto",
|
||||
PACKET_UP: "packet-up",
|
||||
STREAM_UP: "stream-up",
|
||||
STREAM_ONE: "stream-one",
|
||||
};
|
||||
|
||||
const Address_Port_Strategy = {
|
||||
NONE: "none",
|
||||
SrvPortOnly: "srvportonly",
|
||||
SrvAddressOnly: "srvaddressonly",
|
||||
SrvPortAndAddress: "srvportandaddress",
|
||||
TxtPortOnly: "txtportonly",
|
||||
TxtAddressOnly: "txtaddressonly",
|
||||
TxtPortAndAddress: "txtportandaddress"
|
||||
};
|
||||
|
||||
Object.freeze(Protocols);
|
||||
Object.freeze(SSMethods);
|
||||
Object.freeze(TLS_FLOW_CONTROL);
|
||||
@@ -76,7 +103,9 @@ Object.freeze(UTLS_FINGERPRINT);
|
||||
Object.freeze(ALPN_OPTION);
|
||||
Object.freeze(OutboundDomainStrategies);
|
||||
Object.freeze(WireguardDomainStrategy);
|
||||
|
||||
Object.freeze(USERS_SECURITY);
|
||||
Object.freeze(MODE_OPTION);
|
||||
Object.freeze(Address_Port_Strategy);
|
||||
|
||||
class CommonClass {
|
||||
|
||||
@@ -189,16 +218,23 @@ class KcpStreamSettings extends CommonClass {
|
||||
}
|
||||
|
||||
class WsStreamSettings extends CommonClass {
|
||||
constructor(path = '/', host = '') {
|
||||
constructor(
|
||||
path = '/',
|
||||
host = '',
|
||||
heartbeatPeriod = 0,
|
||||
|
||||
) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.heartbeatPeriod = heartbeatPeriod;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new WsStreamSettings(
|
||||
json.path,
|
||||
json.host,
|
||||
json.heartbeatPeriod,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,63 +242,11 @@ class WsStreamSettings extends CommonClass {
|
||||
return {
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
heartbeatPeriod: this.heartbeatPeriod
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class HttpStreamSettings extends CommonClass {
|
||||
constructor(path = '/', host = '') {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new HttpStreamSettings(
|
||||
json.path,
|
||||
json.host ? json.host.join(',') : '',
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
path: this.path,
|
||||
host: ObjectUtil.isEmpty(this.host) ? [''] : this.host.split(','),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class QuicStreamSettings extends CommonClass {
|
||||
constructor(
|
||||
security = 'none',
|
||||
key = '',
|
||||
type = 'none'
|
||||
) {
|
||||
super();
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new QuicStreamSettings(
|
||||
json.security,
|
||||
json.key,
|
||||
json.header ? json.header.type : 'none',
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
security: this.security,
|
||||
key: this.key,
|
||||
header: {
|
||||
type: this.type,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GrpcStreamSettings extends CommonClass {
|
||||
constructor(
|
||||
serviceName = "",
|
||||
@@ -310,17 +294,39 @@ class HttpUpgradeStreamSettings extends CommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
class SplitHTTPStreamSettings extends CommonClass {
|
||||
constructor(path = '/', host = '') {
|
||||
class xHTTPStreamSettings extends CommonClass {
|
||||
constructor(
|
||||
path = '/',
|
||||
host = '',
|
||||
mode = '',
|
||||
noGRPCHeader = false,
|
||||
scMinPostsIntervalMs = "30",
|
||||
xmux = {
|
||||
maxConcurrency: "16-32",
|
||||
maxConnections: 0,
|
||||
cMaxReuseTimes: 0,
|
||||
hMaxRequestTimes: "600-900",
|
||||
hMaxReusableSecs: "1800-3000",
|
||||
hKeepAlivePeriod: 0,
|
||||
},
|
||||
) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.mode = mode;
|
||||
this.noGRPCHeader = noGRPCHeader;
|
||||
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
|
||||
this.xmux = xmux;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new SplitHTTPStreamSettings(
|
||||
return new xHTTPStreamSettings(
|
||||
json.path,
|
||||
json.host,
|
||||
json.mode,
|
||||
json.noGRPCHeader,
|
||||
json.scMinPostsIntervalMs,
|
||||
json.xmux
|
||||
);
|
||||
}
|
||||
|
||||
@@ -328,6 +334,17 @@ class SplitHTTPStreamSettings extends CommonClass {
|
||||
return {
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
mode: this.mode,
|
||||
noGRPCHeader: this.noGRPCHeader,
|
||||
scMinPostsIntervalMs: this.scMinPostsIntervalMs,
|
||||
xmux: {
|
||||
maxConcurrency: this.xmux.maxConcurrency,
|
||||
maxConnections: this.xmux.maxConnections,
|
||||
cMaxReuseTimes: this.xmux.cMaxReuseTimes,
|
||||
hMaxRequestTimes: this.xmux.hMaxRequestTimes,
|
||||
hMaxReusableSecs: this.xmux.hMaxReusableSecs,
|
||||
hKeepAlivePeriod: this.xmux.hKeepAlivePeriod,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -337,13 +354,15 @@ class TlsStreamSettings extends CommonClass {
|
||||
serverName = '',
|
||||
alpn = [],
|
||||
fingerprint = '',
|
||||
allowInsecure = false
|
||||
allowInsecure = false,
|
||||
echConfigList = '',
|
||||
) {
|
||||
super();
|
||||
this.serverName = serverName;
|
||||
this.alpn = alpn;
|
||||
this.fingerprint = fingerprint;
|
||||
this.allowInsecure = allowInsecure;
|
||||
this.echConfigList = echConfigList;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
@@ -352,6 +371,7 @@ class TlsStreamSettings extends CommonClass {
|
||||
json.alpn,
|
||||
json.fingerprint,
|
||||
json.allowInsecure,
|
||||
json.echConfigList,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -361,6 +381,7 @@ class TlsStreamSettings extends CommonClass {
|
||||
alpn: this.alpn,
|
||||
fingerprint: this.fingerprint,
|
||||
allowInsecure: this.allowInsecure,
|
||||
echConfigList: this.echConfigList
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -371,7 +392,8 @@ class RealityStreamSettings extends CommonClass {
|
||||
fingerprint = '',
|
||||
serverName = '',
|
||||
shortId = '',
|
||||
spiderX = '/'
|
||||
spiderX = '',
|
||||
mldsa65Verify = ''
|
||||
) {
|
||||
super();
|
||||
this.publicKey = publicKey;
|
||||
@@ -379,6 +401,7 @@ class RealityStreamSettings extends CommonClass {
|
||||
this.serverName = serverName;
|
||||
this.shortId = shortId
|
||||
this.spiderX = spiderX;
|
||||
this.mldsa65Verify = mldsa65Verify;
|
||||
}
|
||||
static fromJson(json = {}) {
|
||||
return new RealityStreamSettings(
|
||||
@@ -387,6 +410,7 @@ class RealityStreamSettings extends CommonClass {
|
||||
json.serverName,
|
||||
json.shortId,
|
||||
json.spiderX,
|
||||
json.mldsa65Verify
|
||||
);
|
||||
}
|
||||
toJson() {
|
||||
@@ -396,6 +420,7 @@ class RealityStreamSettings extends CommonClass {
|
||||
serverName: this.serverName,
|
||||
shortId: this.shortId,
|
||||
spiderX: this.spiderX,
|
||||
mldsa65Verify: this.mldsa65Verify
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -405,14 +430,16 @@ class SockoptStreamSettings extends CommonClass {
|
||||
tcpFastOpen = false,
|
||||
tcpKeepAliveInterval = 0,
|
||||
tcpMptcp = false,
|
||||
tcpNoDelay = false
|
||||
penetrate = false,
|
||||
addressPortStrategy = Address_Port_Strategy.NONE,
|
||||
) {
|
||||
super();
|
||||
this.dialerProxy = dialerProxy;
|
||||
this.tcpFastOpen = tcpFastOpen;
|
||||
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
|
||||
this.tcpMptcp = tcpMptcp;
|
||||
this.tcpNoDelay = tcpNoDelay;
|
||||
this.penetrate = penetrate;
|
||||
this.addressPortStrategy = addressPortStrategy;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
@@ -422,7 +449,8 @@ class SockoptStreamSettings extends CommonClass {
|
||||
json.tcpFastOpen,
|
||||
json.tcpKeepAliveInterval,
|
||||
json.tcpMptcp,
|
||||
json.tcpNoDelay,
|
||||
json.penetrate,
|
||||
json.addressPortStrategy
|
||||
);
|
||||
}
|
||||
|
||||
@@ -432,7 +460,8 @@ class SockoptStreamSettings extends CommonClass {
|
||||
tcpFastOpen: this.tcpFastOpen,
|
||||
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
||||
tcpMptcp: this.tcpMptcp,
|
||||
tcpNoDelay: this.tcpNoDelay,
|
||||
penetrate: this.penetrate,
|
||||
addressPortStrategy: this.addressPortStrategy
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -446,11 +475,9 @@ class StreamSettings extends CommonClass {
|
||||
tcpSettings = new TcpStreamSettings(),
|
||||
kcpSettings = new KcpStreamSettings(),
|
||||
wsSettings = new WsStreamSettings(),
|
||||
httpSettings = new HttpStreamSettings(),
|
||||
quicSettings = new QuicStreamSettings(),
|
||||
grpcSettings = new GrpcStreamSettings(),
|
||||
httpupgradeSettings = new HttpUpgradeStreamSettings(),
|
||||
splithttpSettings = new SplitHTTPStreamSettings(),
|
||||
xhttpSettings = new xHTTPStreamSettings(),
|
||||
sockopt = undefined,
|
||||
) {
|
||||
super();
|
||||
@@ -461,11 +488,9 @@ class StreamSettings extends CommonClass {
|
||||
this.tcp = tcpSettings;
|
||||
this.kcp = kcpSettings;
|
||||
this.ws = wsSettings;
|
||||
this.http = httpSettings;
|
||||
this.quic = quicSettings;
|
||||
this.grpc = grpcSettings;
|
||||
this.httpupgrade = httpupgradeSettings;
|
||||
this.splithttp = splithttpSettings;
|
||||
this.xhttp = xhttpSettings;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
@@ -494,11 +519,9 @@ class StreamSettings extends CommonClass {
|
||||
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||
WsStreamSettings.fromJson(json.wsSettings),
|
||||
HttpStreamSettings.fromJson(json.httpSettings),
|
||||
QuicStreamSettings.fromJson(json.quicSettings),
|
||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||
SplitHTTPStreamSettings.fromJson(json.splithttpSettings),
|
||||
xHTTPStreamSettings.fromJson(json.xhttpSettings),
|
||||
SockoptStreamSettings.fromJson(json.sockopt),
|
||||
);
|
||||
}
|
||||
@@ -513,11 +536,9 @@ class StreamSettings extends CommonClass {
|
||||
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||
splithttpSettings: network === 'splithttp' ? this.splithttp.toJson() : undefined,
|
||||
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
|
||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||
};
|
||||
}
|
||||
@@ -555,7 +576,7 @@ class Mux extends CommonClass {
|
||||
class Outbound extends CommonClass {
|
||||
constructor(
|
||||
tag = '',
|
||||
protocol = Protocols.VMess,
|
||||
protocol = Protocols.VLESS,
|
||||
settings = null,
|
||||
streamSettings = new StreamSettings(),
|
||||
sendThrough,
|
||||
@@ -582,7 +603,7 @@ class Outbound extends CommonClass {
|
||||
|
||||
canEnableTls() {
|
||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
||||
return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade", "splithttp"].includes(this.stream.network);
|
||||
return ["tcp", "ws", "http", "grpc", "httpupgrade", "xhttp"].includes(this.stream.network);
|
||||
}
|
||||
|
||||
//this is used for xtls-rprx-vision
|
||||
@@ -595,7 +616,7 @@ class Outbound extends CommonClass {
|
||||
|
||||
canEnableReality() {
|
||||
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||
return ["tcp", "http", "grpc"].includes(this.stream.network);
|
||||
return ["tcp", "http", "grpc", "xhttp"].includes(this.stream.network);
|
||||
}
|
||||
|
||||
canEnableStream() {
|
||||
@@ -603,11 +624,27 @@ class Outbound extends CommonClass {
|
||||
}
|
||||
|
||||
canEnableMux() {
|
||||
if (this.settings.flow && this.settings.flow != '') {
|
||||
// Disable Mux if flow is set
|
||||
if (this.settings.flow && this.settings.flow !== '') {
|
||||
this.mux.enabled = false;
|
||||
return false;
|
||||
}
|
||||
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.HTTP, Protocols.Socks].includes(this.protocol);
|
||||
|
||||
// Disable Mux if network is xhttp
|
||||
if (this.stream.network === 'xhttp') {
|
||||
this.mux.enabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow Mux only for these protocols
|
||||
return [
|
||||
Protocols.VMess,
|
||||
Protocols.VLESS,
|
||||
Protocols.Trojan,
|
||||
Protocols.Shadowsocks,
|
||||
Protocols.HTTP,
|
||||
Protocols.Socks
|
||||
].includes(this.protocol);
|
||||
}
|
||||
|
||||
hasVnext() {
|
||||
@@ -693,22 +730,12 @@ class Outbound extends CommonClass {
|
||||
stream.seed = json.path;
|
||||
} else if (network === 'ws') {
|
||||
stream.ws = new WsStreamSettings(json.path, json.host);
|
||||
} else if (network === 'http' || network == 'h2') {
|
||||
stream.network = 'http'
|
||||
stream.http = new HttpStreamSettings(
|
||||
json.path,
|
||||
json.host);
|
||||
} else if (network === 'quic') {
|
||||
stream.quic = new QuicStreamSettings(
|
||||
json.host ? json.host : 'none',
|
||||
json.path,
|
||||
json.type ? json.type : 'none');
|
||||
} else if (network === 'grpc') {
|
||||
stream.grpc = new GrpcStreamSettings(json.path, json.authority, json.type == 'multi');
|
||||
} else if (network === 'httpupgrade') {
|
||||
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path, json.host);
|
||||
} else if (network === 'splithttp') {
|
||||
stream.splithttp = new SplitHTTPStreamSettings(json.path, json.host);
|
||||
} else if (network === 'xhttp') {
|
||||
stream.xhttp = new xHTTPStreamSettings(json.path, json.host, json.mode);
|
||||
}
|
||||
|
||||
if (json.tls && json.tls == 'tls') {
|
||||
@@ -721,7 +748,7 @@ class Outbound extends CommonClass {
|
||||
|
||||
const port = json.port * 1;
|
||||
|
||||
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, port, json.id), stream);
|
||||
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, port, json.id, json.scy), stream);
|
||||
}
|
||||
|
||||
static fromParamLink(link) {
|
||||
@@ -733,6 +760,7 @@ class Outbound extends CommonClass {
|
||||
let headerType = url.searchParams.get('headerType') ?? undefined;
|
||||
let host = url.searchParams.get('host') ?? undefined;
|
||||
let path = url.searchParams.get('path') ?? undefined;
|
||||
let mode = url.searchParams.get('mode') ?? undefined;
|
||||
|
||||
if (type === 'tcp' || type === 'none') {
|
||||
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
|
||||
@@ -742,13 +770,6 @@ class Outbound extends CommonClass {
|
||||
stream.kcp.seed = path;
|
||||
} else if (type === 'ws') {
|
||||
stream.ws = new WsStreamSettings(path, host);
|
||||
} else if (type === 'http' || type == 'h2') {
|
||||
stream.http = new HttpStreamSettings(path, host);
|
||||
} else if (type === 'quic') {
|
||||
stream.quic = new QuicStreamSettings(
|
||||
url.searchParams.get('quicSecurity') ?? 'none',
|
||||
url.searchParams.get('key') ?? '',
|
||||
headerType ?? 'none');
|
||||
} else if (type === 'grpc') {
|
||||
stream.grpc = new GrpcStreamSettings(
|
||||
url.searchParams.get('serviceName') ?? '',
|
||||
@@ -756,8 +777,8 @@ class Outbound extends CommonClass {
|
||||
url.searchParams.get('mode') == 'multi');
|
||||
} else if (type === 'httpupgrade') {
|
||||
stream.httpupgrade = new HttpUpgradeStreamSettings(path, host);
|
||||
} else if (type === 'splithttp') {
|
||||
stream.splithttp = new SplitHTTPStreamSettings(path, host);
|
||||
} else if (type === 'xhttp') {
|
||||
stream.xhttp = new xHTTPStreamSettings(path, host, mode);
|
||||
}
|
||||
|
||||
if (security == 'tls') {
|
||||
@@ -765,7 +786,8 @@ class Outbound extends CommonClass {
|
||||
let alpn = url.searchParams.get('alpn');
|
||||
let allowInsecure = url.searchParams.get('allowInsecure');
|
||||
let sni = url.searchParams.get('sni') ?? '';
|
||||
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
|
||||
let ech = url.searchParams.get('ech') ?? '';
|
||||
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1, ech);
|
||||
}
|
||||
|
||||
if (security == 'reality') {
|
||||
@@ -774,7 +796,8 @@ class Outbound extends CommonClass {
|
||||
let sni = url.searchParams.get('sni') ?? '';
|
||||
let sid = url.searchParams.get('sid') ?? '';
|
||||
let spx = url.searchParams.get('spx') ?? '';
|
||||
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
|
||||
let pqv = url.searchParams.get('pqv') ?? '';
|
||||
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx, pqv);
|
||||
}
|
||||
|
||||
const regex = /([^@]+):\/\/([^@]+)@(.+):(\d+)(.*)$/;
|
||||
@@ -790,7 +813,7 @@ class Outbound extends CommonClass {
|
||||
var settings;
|
||||
switch (protocol) {
|
||||
case Protocols.VLESS:
|
||||
settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '');
|
||||
settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '', url.searchParams.get('encryption') ?? 'none');
|
||||
break;
|
||||
case Protocols.Trojan:
|
||||
settings = new Outbound.TrojanSettings(address, port, userData);
|
||||
@@ -852,32 +875,58 @@ Outbound.Settings = class extends CommonClass {
|
||||
}
|
||||
};
|
||||
Outbound.FreedomSettings = class extends CommonClass {
|
||||
constructor(domainStrategy = '', fragment = {}) {
|
||||
constructor(
|
||||
domainStrategy = '',
|
||||
redirect = '',
|
||||
fragment = {},
|
||||
noises = []
|
||||
) {
|
||||
super();
|
||||
this.domainStrategy = domainStrategy;
|
||||
this.redirect = redirect;
|
||||
this.fragment = fragment;
|
||||
this.noises = noises;
|
||||
}
|
||||
|
||||
addNoise() {
|
||||
this.noises.push(new Outbound.FreedomSettings.Noise());
|
||||
}
|
||||
|
||||
delNoise(index) {
|
||||
this.noises.splice(index, 1);
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Outbound.FreedomSettings(
|
||||
json.domainStrategy,
|
||||
json.redirect,
|
||||
json.fragment ? Outbound.FreedomSettings.Fragment.fromJson(json.fragment) : undefined,
|
||||
json.noises ? json.noises.map(noise => Outbound.FreedomSettings.Noise.fromJson(noise)) : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
domainStrategy: ObjectUtil.isEmpty(this.domainStrategy) ? undefined : this.domainStrategy,
|
||||
redirect: ObjectUtil.isEmpty(this.redirect) ? undefined: this.redirect,
|
||||
fragment: Object.keys(this.fragment).length === 0 ? undefined : this.fragment,
|
||||
noises: this.noises.length === 0 ? undefined : Outbound.FreedomSettings.Noise.toJsonArray(this.noises),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
||||
constructor(packets = '1-3', length = '', interval = '') {
|
||||
constructor(
|
||||
packets = '1-3',
|
||||
length = '',
|
||||
interval = '',
|
||||
maxSplit = ''
|
||||
) {
|
||||
super();
|
||||
this.packets = packets;
|
||||
this.length = length;
|
||||
this.interval = interval;
|
||||
this.maxSplit = maxSplit;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
@@ -885,9 +934,44 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
||||
json.packets,
|
||||
json.length,
|
||||
json.interval,
|
||||
json.maxSplit
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Outbound.FreedomSettings.Noise = class extends CommonClass {
|
||||
constructor(
|
||||
type = 'rand',
|
||||
packet = '10-20',
|
||||
delay = '10-16',
|
||||
applyTo = 'ip'
|
||||
) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.packet = packet;
|
||||
this.delay = delay;
|
||||
this.applyTo = applyTo;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Outbound.FreedomSettings.Noise(
|
||||
json.type,
|
||||
json.packet,
|
||||
json.delay,
|
||||
json.applyTo
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
type: this.type,
|
||||
packet: this.packet,
|
||||
delay: this.delay,
|
||||
applyTo: this.applyTo
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Outbound.BlackholeSettings = class extends CommonClass {
|
||||
constructor(type) {
|
||||
super();
|
||||
@@ -907,11 +991,19 @@ Outbound.BlackholeSettings = class extends CommonClass {
|
||||
}
|
||||
};
|
||||
Outbound.DNSSettings = class extends CommonClass {
|
||||
constructor(network = 'udp', address = '1.1.1.1', port = 53) {
|
||||
constructor(
|
||||
network = 'udp',
|
||||
address = '',
|
||||
port = 53,
|
||||
nonIPQuery = 'reject',
|
||||
blockTypes = []
|
||||
) {
|
||||
super();
|
||||
this.network = network;
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.nonIPQuery = nonIPQuery;
|
||||
this.blockTypes = blockTypes;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
@@ -919,15 +1011,18 @@ Outbound.DNSSettings = class extends CommonClass {
|
||||
json.network,
|
||||
json.address,
|
||||
json.port,
|
||||
json.nonIPQuery,
|
||||
json.blockTypes,
|
||||
);
|
||||
}
|
||||
};
|
||||
Outbound.VmessSettings = class extends CommonClass {
|
||||
constructor(address, port, id) {
|
||||
constructor(address, port, id, security) {
|
||||
super();
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.id = id;
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
@@ -936,6 +1031,7 @@ Outbound.VmessSettings = class extends CommonClass {
|
||||
json.vnext[0].address,
|
||||
json.vnext[0].port,
|
||||
json.vnext[0].users[0].id,
|
||||
json.vnext[0].users[0].security,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -944,19 +1040,19 @@ Outbound.VmessSettings = class extends CommonClass {
|
||||
vnext: [{
|
||||
address: this.address,
|
||||
port: this.port,
|
||||
users: [{ id: this.id }],
|
||||
users: [{ id: this.id, security: this.security }],
|
||||
}],
|
||||
};
|
||||
}
|
||||
};
|
||||
Outbound.VLESSSettings = class extends CommonClass {
|
||||
constructor(address, port, id, flow, encryption = 'none') {
|
||||
constructor(address, port, id, flow, encryption) {
|
||||
super();
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.id = id;
|
||||
this.flow = flow;
|
||||
this.encryption = encryption
|
||||
this.encryption = encryption;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
@@ -975,7 +1071,7 @@ Outbound.VLESSSettings = class extends CommonClass {
|
||||
vnext: [{
|
||||
address: this.address,
|
||||
port: this.port,
|
||||
users: [{ id: this.id, flow: this.flow, encryption: 'none', }],
|
||||
users: [{ id: this.id, flow: this.flow, encryption: this.encryption }],
|
||||
}],
|
||||
};
|
||||
}
|
||||
@@ -1115,7 +1211,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||
domainStrategy = '',
|
||||
reserved = '',
|
||||
peers = [new Outbound.WireguardSettings.Peer()],
|
||||
kernelMode = false
|
||||
noKernelTun = false,
|
||||
) {
|
||||
super();
|
||||
this.mtu = mtu;
|
||||
@@ -1126,7 +1222,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||
this.domainStrategy = domainStrategy;
|
||||
this.reserved = Array.isArray(reserved) ? reserved.join(',') : reserved;
|
||||
this.peers = peers;
|
||||
this.kernelMode = kernelMode;
|
||||
this.noKernelTun = noKernelTun;
|
||||
}
|
||||
|
||||
addPeer() {
|
||||
@@ -1146,7 +1242,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||
json.domainStrategy,
|
||||
json.reserved,
|
||||
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
|
||||
json.kernelMode,
|
||||
json.noKernelTun,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1159,7 +1255,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
||||
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
|
||||
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||
kernelMode: this.kernelMode,
|
||||
noKernelTun: this.noKernelTun,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ class AllSetting {
|
||||
this.webCertFile = "";
|
||||
this.webKeyFile = "";
|
||||
this.webBasePath = "/";
|
||||
this.sessionMaxAge = 0;
|
||||
this.sessionMaxAge = 360;
|
||||
this.pageSize = 50;
|
||||
this.expireDiff = 0;
|
||||
this.trafficDiff = 0;
|
||||
@@ -16,20 +16,25 @@ class AllSetting {
|
||||
this.tgBotEnable = false;
|
||||
this.tgBotToken = "";
|
||||
this.tgBotProxy = "";
|
||||
this.tgBotAPIServer = "";
|
||||
this.tgBotChatId = "";
|
||||
this.tgRunTime = "@daily";
|
||||
this.tgBotBackup = false;
|
||||
this.tgBotLoginNotify = true;
|
||||
this.tgCpu = 80;
|
||||
this.tgLang = "en-US";
|
||||
this.twoFactorEnable = false;
|
||||
this.twoFactorToken = "";
|
||||
this.xrayTemplateConfig = "";
|
||||
this.secretEnable = false;
|
||||
this.subEnable = false;
|
||||
this.subTitle = "";
|
||||
this.subListen = "";
|
||||
this.subPort = 2096;
|
||||
this.subPath = "/sub/";
|
||||
this.subJsonPath = "/json/";
|
||||
this.subDomain = "";
|
||||
this.externalTrafficInformEnable = false;
|
||||
this.externalTrafficInformURI = "";
|
||||
this.subCertFile = "";
|
||||
this.subKeyFile = "";
|
||||
this.subUpdates = 12;
|
||||
@@ -38,10 +43,11 @@ class AllSetting {
|
||||
this.subURI = "";
|
||||
this.subJsonURI = "";
|
||||
this.subJsonFragment = "";
|
||||
this.subJsonNoises = "";
|
||||
this.subJsonMux = "";
|
||||
this.subJsonRules = "";
|
||||
|
||||
this.timeLocation = "Asia/Tehran";
|
||||
this.timeLocation = "Local";
|
||||
|
||||
if (data == null) {
|
||||
return
|
||||
|
||||
125
web/assets/js/subscription.js
Normal file
@@ -0,0 +1,125 @@
|
||||
(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://')) {
|
||||
// vless://<id>@host:port?...#name
|
||||
const hashIdx = link.indexOf('#');
|
||||
if (hashIdx !== -1) return decodeURIComponent(link.substring(hashIdx + 1));
|
||||
// email sometimes in query params like sni or remark
|
||||
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');
|
||||
}
|
||||
// else take user@host
|
||||
const at = link.indexOf('@');
|
||||
const protSep = link.indexOf('://');
|
||||
if (at !== -1 && protSep !== -1) return link.substring(protSep + 3, at);
|
||||
} else if (link.startsWith('ss://')) {
|
||||
// shadowsocks: label often after #
|
||||
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();
|
||||
// Discover subJsonUrl if provided via template bootstrap
|
||||
const tpl = document.getElementById('subscription-data');
|
||||
const sj = tpl ? tpl.getAttribute('data-subjson-url') : '';
|
||||
if (sj) this.app.subJsonUrl = sj;
|
||||
drawQR(this.app.subUrl);
|
||||
// Draw second QR if available
|
||||
try { new QRious({ element: document.getElementById('qrcode-subjson'), value: this.app.subJsonUrl || '', size: 220 }); } catch (e) { /* ignore */ }
|
||||
// Track viewport width for responsive behavior
|
||||
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;
|
||||
},
|
||||
},
|
||||
methods: { renderLink, copy, open, linkName, i18nLabel(key) { return '{{ i18n "' + key + '" }}'; } },
|
||||
});
|
||||
})();
|
||||
@@ -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}`;
|
||||
}
|
||||
@@ -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());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -134,7 +134,7 @@ class DateUtil {
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
895
web/assets/js/util/index.js
Normal file
@@ -0,0 +1,895 @@
|
||||
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) {
|
||||
for (const key in a) {
|
||||
if (!a.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
if (!b.hasOwnProperty(key)) {
|
||||
return false;
|
||||
} else if (a[key] !== b[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const key in b) {
|
||||
if (!b.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
if (!a.hasOwnProperty(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();
|
||||
}
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
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)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
const lengths = [2, 4, 6, 8, 10, 12, 14, 16];
|
||||
for (let i = lengths.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[lengths[i], lengths[j]] = [lengths[j], lengths[i]];
|
||||
}
|
||||
|
||||
let shortIds = [];
|
||||
for (let length of lengths) {
|
||||
let shortId = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
shortId += seq[this.randomInt(16)];
|
||||
}
|
||||
shortIds.push(shortId);
|
||||
}
|
||||
return shortIds;
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
3
web/assets/moment/moment.min.js
vendored
19
web/assets/otpauth/otpauth.umd.min.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
//! otpauth 9.4.0 | (c) Héctor Molinero Fernández | MIT | https://github.com/hectorm/otpauth
|
||||
//! noble-hashes 1.7.1 | (c) Paul Miller | MIT | https://github.com/paulmillr/noble-hashes
|
||||
/// <reference types="./otpauth.d.ts" />
|
||||
// @ts-nocheck
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).OTPAuth={})}(this,(function(t){"use strict";function e(t){if(!Number.isSafeInteger(t)||t<0)throw new Error("positive integer expected, got "+t)}function s(t,...e){if(!((s=t)instanceof Uint8Array||ArrayBuffer.isView(s)&&"Uint8Array"===s.constructor.name))throw new Error("Uint8Array expected");var s;if(e.length>0&&!e.includes(t.length))throw new Error("Uint8Array expected of length "+e+", got length="+t.length)}function i(t,e=!0){if(t.destroyed)throw new Error("Hash instance has been destroyed");if(e&&t.finished)throw new Error("Hash#digest() has already been called")}function r(t,e){s(t);const i=e.outputLen;if(t.length<i)throw new Error("digestInto() expects output buffer of length at least "+i)}function n(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function o(t,e){return t<<32-e|t>>>e}function h(t,e){return t<<e|t>>>32-e>>>0}const a=(()=>68===new Uint8Array(new Uint32Array([287454020]).buffer)[0])();function l(t){for(let s=0;s<t.length;s++)t[s]=(e=t[s])<<24&4278190080|e<<8&16711680|e>>>8&65280|e>>>24&255;var e}function c(t){return"string"==typeof t&&(t=function(t){if("string"!=typeof t)throw new Error("utf8ToBytes expected string, got "+typeof t);return new Uint8Array((new TextEncoder).encode(t))}(t)),s(t),t}class u{clone(){return this._cloneInto()}}function d(t){const e=e=>t().update(c(e)).digest(),s=t();return e.outputLen=s.outputLen,e.blockLen=s.blockLen,e.create=()=>t(),e}class f extends u{update(t){return i(this),this.iHash.update(t),this}digestInto(t){i(this),s(t,this.outputLen),this.finished=!0,this.iHash.digestInto(t),this.oHash.update(t),this.oHash.digestInto(t),this.destroy()}digest(){const t=new Uint8Array(this.oHash.outputLen);return this.digestInto(t),t}_cloneInto(t){t||(t=Object.create(Object.getPrototypeOf(this),{}));const{oHash:e,iHash:s,finished:i,destroyed:r,blockLen:n,outputLen:o}=this
|
||||
;return t.finished=i,t.destroyed=r,t.blockLen=n,t.outputLen=o,t.oHash=e._cloneInto(t.oHash),t.iHash=s._cloneInto(t.iHash),t}destroy(){this.destroyed=!0,this.oHash.destroy(),this.iHash.destroy()}constructor(t,s){super(),this.finished=!1,this.destroyed=!1,function(t){if("function"!=typeof t||"function"!=typeof t.create)throw new Error("Hash should be wrapped by utils.wrapConstructor");e(t.outputLen),e(t.blockLen)}(t);const i=c(s);if(this.iHash=t.create(),"function"!=typeof this.iHash.update)throw new Error("Expected instance of class which extends utils.Hash");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;const r=this.blockLen,n=new Uint8Array(r);n.set(i.length>r?t.create().update(i).digest():i);for(let t=0;t<n.length;t++)n[t]^=54;this.iHash.update(n),this.oHash=t.create();for(let t=0;t<n.length;t++)n[t]^=106;this.oHash.update(n),n.fill(0)}}const b=(t,e,s)=>new f(t,e).update(s).digest();function g(t,e,s){return t&e^~t&s}function p(t,e,s){return t&e^t&s^e&s}b.create=(t,e)=>new f(t,e);class w extends u{update(t){i(this);const{view:e,buffer:s,blockLen:r}=this,o=(t=c(t)).length;for(let i=0;i<o;){const h=Math.min(r-this.pos,o-i);if(h!==r)s.set(t.subarray(i,i+h),this.pos),this.pos+=h,i+=h,this.pos===r&&(this.process(e,0),this.pos=0);else{const e=n(t);for(;r<=o-i;i+=r)this.process(e,i)}}return this.length+=t.length,this.roundClean(),this}digestInto(t){i(this),r(t,this),this.finished=!0;const{buffer:e,view:s,blockLen:o,isLE:h}=this;let{pos:a}=this;e[a++]=128,this.buffer.subarray(a).fill(0),this.padOffset>o-a&&(this.process(s,0),a=0);for(let t=a;t<o;t++)e[t]=0;!function(t,e,s,i){if("function"==typeof t.setBigUint64)return t.setBigUint64(e,s,i);const r=BigInt(32),n=BigInt(4294967295),o=Number(s>>r&n),h=Number(s&n),a=i?4:0,l=i?0:4;t.setUint32(e+a,o,i),t.setUint32(e+l,h,i)}(s,o-8,BigInt(8*this.length),h),this.process(s,0);const l=n(t),c=this.outputLen;if(c%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const u=c/4,d=this.get()
|
||||
;if(u>d.length)throw new Error("_sha2: outputLen bigger than state");for(let t=0;t<u;t++)l.setUint32(4*t,d[t],h)}digest(){const{buffer:t,outputLen:e}=this;this.digestInto(t);const s=t.slice(0,e);return this.destroy(),s}_cloneInto(t){t||(t=new this.constructor),t.set(...this.get());const{blockLen:e,buffer:s,length:i,finished:r,destroyed:n,pos:o}=this;return t.length=i,t.pos=o,t.finished=r,t.destroyed=n,i%e&&t.buffer.set(s),t}constructor(t,e,s,i){super(),this.blockLen=t,this.outputLen=e,this.padOffset=s,this.isLE=i,this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.buffer=new Uint8Array(t),this.view=n(this.buffer)}}const y=new Uint32Array([1732584193,4023233417,2562383102,271733878,3285377520]),x=new Uint32Array(80);class A extends w{get(){const{A:t,B:e,C:s,D:i,E:r}=this;return[t,e,s,i,r]}set(t,e,s,i,r){this.A=0|t,this.B=0|e,this.C=0|s,this.D=0|i,this.E=0|r}process(t,e){for(let s=0;s<16;s++,e+=4)x[s]=t.getUint32(e,!1);for(let t=16;t<80;t++)x[t]=h(x[t-3]^x[t-8]^x[t-14]^x[t-16],1);let{A:s,B:i,C:r,D:n,E:o}=this;for(let t=0;t<80;t++){let e,a;t<20?(e=g(i,r,n),a=1518500249):t<40?(e=i^r^n,a=1859775393):t<60?(e=p(i,r,n),a=2400959708):(e=i^r^n,a=3395469782);const l=h(s,5)+e+o+a+x[t]|0;o=n,n=r,r=h(i,30),i=s,s=l}s=s+this.A|0,i=i+this.B|0,r=r+this.C|0,n=n+this.D|0,o=o+this.E|0,this.set(s,i,r,n,o)}roundClean(){x.fill(0)}destroy(){this.set(0,0,0,0,0),this.buffer.fill(0)}constructor(){super(64,20,8,!1),this.A=0|y[0],this.B=0|y[1],this.C=0|y[2],this.D=0|y[3],this.E=0|y[4]}}
|
||||
const m=d((()=>new A)),H=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),L=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),I=new Uint32Array(64);class S extends w{get(){const{A:t,B:e,C:s,D:i,E:r,F:n,G:o,H:h}=this;return[t,e,s,i,r,n,o,h]}set(t,e,s,i,r,n,o,h){this.A=0|t,this.B=0|e,this.C=0|s,this.D=0|i,this.E=0|r,this.F=0|n,this.G=0|o,this.H=0|h}process(t,e){for(let s=0;s<16;s++,e+=4)I[s]=t.getUint32(e,!1);for(let t=16;t<64;t++){const e=I[t-15],s=I[t-2],i=o(e,7)^o(e,18)^e>>>3,r=o(s,17)^o(s,19)^s>>>10;I[t]=r+I[t-7]+i+I[t-16]|0}let{A:s,B:i,C:r,D:n,E:h,F:a,G:l,H:c}=this;for(let t=0;t<64;t++){const e=c+(o(h,6)^o(h,11)^o(h,25))+g(h,a,l)+H[t]+I[t]|0,u=(o(s,2)^o(s,13)^o(s,22))+p(s,i,r)|0;c=l,l=a,a=h,h=n+e|0,n=r,r=i,i=s,s=e+u|0}s=s+this.A|0,i=i+this.B|0,r=r+this.C|0,n=n+this.D|0,h=h+this.E|0,a=a+this.F|0,l=l+this.G|0,c=c+this.H|0,this.set(s,i,r,n,h,a,l,c)}roundClean(){I.fill(0)}destroy(){this.set(0,0,0,0,0,0,0,0),this.buffer.fill(0)}constructor(){super(64,32,8,!1),this.A=0|L[0],this.B=0|L[1],this.C=0|L[2],this.D=0|L[3],this.E=0|L[4],this.F=0|L[5],this.G=0|L[6],this.H=0|L[7]}}class B extends S{constructor(){super(),this.A=-1056596264,this.B=914150663,this.C=812702999,this.D=-150054599,this.E=-4191439,this.F=1750603025,this.G=1694076839,this.H=-1090891868,this.outputLen=28}}
|
||||
const E=d((()=>new S)),U=d((()=>new B)),C=BigInt(2**32-1),O=BigInt(32);function v(t,e=!1){return e?{h:Number(t&C),l:Number(t>>O&C)}:{h:0|Number(t>>O&C),l:0|Number(t&C)}}function k(t,e=!1){let s=new Uint32Array(t.length),i=new Uint32Array(t.length);for(let r=0;r<t.length;r++){const{h:n,l:o}=v(t[r],e);[s[r],i[r]]=[n,o]}return[s,i]}const T=(t,e,s)=>t<<s|e>>>32-s,$=(t,e,s)=>e<<s|t>>>32-s,D=(t,e,s)=>e<<s-32|t>>>64-s,_=(t,e,s)=>t<<s-32|e>>>64-s,F={fromBig:v,split:k,toBig:(t,e)=>BigInt(t>>>0)<<O|BigInt(e>>>0),shrSH:(t,e,s)=>t>>>s,shrSL:(t,e,s)=>t<<32-s|e>>>s,rotrSH:(t,e,s)=>t>>>s|e<<32-s,rotrSL:(t,e,s)=>t<<32-s|e>>>s,rotrBH:(t,e,s)=>t<<64-s|e>>>s-32,rotrBL:(t,e,s)=>t>>>s-32|e<<64-s,rotr32H:(t,e)=>e,rotr32L:(t,e)=>t,rotlSH:T,rotlSL:$,rotlBH:D,rotlBL:_,add:function(t,e,s,i){const r=(e>>>0)+(i>>>0);return{h:t+s+(r/2**32|0)|0,l:0|r}},add3L:(t,e,s)=>(t>>>0)+(e>>>0)+(s>>>0),add3H:(t,e,s,i)=>e+s+i+(t/2**32|0)|0,add4L:(t,e,s,i)=>(t>>>0)+(e>>>0)+(s>>>0)+(i>>>0),add4H:(t,e,s,i,r)=>e+s+i+r+(t/2**32|0)|0,add5H:(t,e,s,i,r,n)=>e+s+i+r+n+(t/2**32|0)|0,add5L:(t,e,s,i,r)=>(t>>>0)+(e>>>0)+(s>>>0)+(i>>>0)+(r>>>0)
|
||||
},[G,P]=(()=>F.split(["0x428a2f98d728ae22","0x7137449123ef65cd","0xb5c0fbcfec4d3b2f","0xe9b5dba58189dbbc","0x3956c25bf348b538","0x59f111f1b605d019","0x923f82a4af194f9b","0xab1c5ed5da6d8118","0xd807aa98a3030242","0x12835b0145706fbe","0x243185be4ee4b28c","0x550c7dc3d5ffb4e2","0x72be5d74f27b896f","0x80deb1fe3b1696b1","0x9bdc06a725c71235","0xc19bf174cf692694","0xe49b69c19ef14ad2","0xefbe4786384f25e3","0x0fc19dc68b8cd5b5","0x240ca1cc77ac9c65","0x2de92c6f592b0275","0x4a7484aa6ea6e483","0x5cb0a9dcbd41fbd4","0x76f988da831153b5","0x983e5152ee66dfab","0xa831c66d2db43210","0xb00327c898fb213f","0xbf597fc7beef0ee4","0xc6e00bf33da88fc2","0xd5a79147930aa725","0x06ca6351e003826f","0x142929670a0e6e70","0x27b70a8546d22ffc","0x2e1b21385c26c926","0x4d2c6dfc5ac42aed","0x53380d139d95b3df","0x650a73548baf63de","0x766a0abb3c77b2a8","0x81c2c92e47edaee6","0x92722c851482353b","0xa2bfe8a14cf10364","0xa81a664bbc423001","0xc24b8b70d0f89791","0xc76c51a30654be30","0xd192e819d6ef5218","0xd69906245565a910","0xf40e35855771202a","0x106aa07032bbd1b8","0x19a4c116b8d2d0c8","0x1e376c085141ab53","0x2748774cdf8eeb99","0x34b0bcb5e19b48a8","0x391c0cb3c5c95a63","0x4ed8aa4ae3418acb","0x5b9cca4f7763e373","0x682e6ff3d6b2b8a3","0x748f82ee5defb2fc","0x78a5636f43172f60","0x84c87814a1f0ab72","0x8cc702081a6439ec","0x90befffa23631e28","0xa4506cebde82bde9","0xbef9a3f7b2c67915","0xc67178f2e372532b","0xca273eceea26619c","0xd186b8c721c0c207","0xeada7dd6cde0eb1e","0xf57d4f7fee6ed178","0x06f067aa72176fba","0x0a637dc5a2c898a6","0x113f9804bef90dae","0x1b710b35131c471b","0x28db77f523047d84","0x32caab7b40c72493","0x3c9ebe0a15c9bebc","0x431d67c49c100d4c","0x4cc5d4becb3e42b6","0x597f299cfc657e2a","0x5fcb6fab3ad6faec","0x6c44198c4a475817"].map((t=>BigInt(t)))))(),j=new Uint32Array(80),M=new Uint32Array(80);class R extends w{get(){const{Ah:t,Al:e,Bh:s,Bl:i,Ch:r,Cl:n,Dh:o,Dl:h,Eh:a,El:l,Fh:c,Fl:u,Gh:d,Gl:f,Hh:b,Hl:g}=this;return[t,e,s,i,r,n,o,h,a,l,c,u,d,f,b,g]}set(t,e,s,i,r,n,o,h,a,l,c,u,d,f,b,g){this.Ah=0|t,this.Al=0|e,this.Bh=0|s,this.Bl=0|i,this.Ch=0|r,this.Cl=0|n,this.Dh=0|o,
|
||||
this.Dl=0|h,this.Eh=0|a,this.El=0|l,this.Fh=0|c,this.Fl=0|u,this.Gh=0|d,this.Gl=0|f,this.Hh=0|b,this.Hl=0|g}process(t,e){for(let s=0;s<16;s++,e+=4)j[s]=t.getUint32(e),M[s]=t.getUint32(e+=4);for(let t=16;t<80;t++){const e=0|j[t-15],s=0|M[t-15],i=F.rotrSH(e,s,1)^F.rotrSH(e,s,8)^F.shrSH(e,s,7),r=F.rotrSL(e,s,1)^F.rotrSL(e,s,8)^F.shrSL(e,s,7),n=0|j[t-2],o=0|M[t-2],h=F.rotrSH(n,o,19)^F.rotrBH(n,o,61)^F.shrSH(n,o,6),a=F.rotrSL(n,o,19)^F.rotrBL(n,o,61)^F.shrSL(n,o,6),l=F.add4L(r,a,M[t-7],M[t-16]),c=F.add4H(l,i,h,j[t-7],j[t-16]);j[t]=0|c,M[t]=0|l}let{Ah:s,Al:i,Bh:r,Bl:n,Ch:o,Cl:h,Dh:a,Dl:l,Eh:c,El:u,Fh:d,Fl:f,Gh:b,Gl:g,Hh:p,Hl:w}=this;for(let t=0;t<80;t++){const e=F.rotrSH(c,u,14)^F.rotrSH(c,u,18)^F.rotrBH(c,u,41),y=F.rotrSL(c,u,14)^F.rotrSL(c,u,18)^F.rotrBL(c,u,41),x=c&d^~c&b,A=u&f^~u&g,m=F.add5L(w,y,A,P[t],M[t]),H=F.add5H(m,p,e,x,G[t],j[t]),L=0|m,I=F.rotrSH(s,i,28)^F.rotrBH(s,i,34)^F.rotrBH(s,i,39),S=F.rotrSL(s,i,28)^F.rotrBL(s,i,34)^F.rotrBL(s,i,39),B=s&r^s&o^r&o,E=i&n^i&h^n&h;p=0|b,w=0|g,b=0|d,g=0|f,d=0|c,f=0|u,({h:c,l:u}=F.add(0|a,0|l,0|H,0|L)),a=0|o,l=0|h,o=0|r,h=0|n,r=0|s,n=0|i;const U=F.add3L(L,S,E);s=F.add3H(U,H,I,B),i=0|U}({h:s,l:i}=F.add(0|this.Ah,0|this.Al,0|s,0|i)),({h:r,l:n}=F.add(0|this.Bh,0|this.Bl,0|r,0|n)),({h:o,l:h}=F.add(0|this.Ch,0|this.Cl,0|o,0|h)),({h:a,l}=F.add(0|this.Dh,0|this.Dl,0|a,0|l)),({h:c,l:u}=F.add(0|this.Eh,0|this.El,0|c,0|u)),({h:d,l:f}=F.add(0|this.Fh,0|this.Fl,0|d,0|f)),({h:b,l:g}=F.add(0|this.Gh,0|this.Gl,0|b,0|g)),({h:p,l:w}=F.add(0|this.Hh,0|this.Hl,0|p,0|w)),this.set(s,i,r,n,o,h,a,l,c,u,d,f,b,g,p,w)}roundClean(){j.fill(0),M.fill(0)}destroy(){this.buffer.fill(0),this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)}constructor(){super(128,64,16,!1),this.Ah=1779033703,this.Al=-205731576,this.Bh=-1150833019,this.Bl=-2067093701,this.Ch=1013904242,this.Cl=-23791573,this.Dh=-1521486534,this.Dl=1595750129,this.Eh=1359893119,this.El=-1377402159,this.Fh=-1694144372,this.Fl=725511199,this.Gh=528734635,this.Gl=-79577749,this.Hh=1541459225,this.Hl=327033209}}class N extends R{constructor(){super(),
|
||||
this.Ah=-876896931,this.Al=-1056596264,this.Bh=1654270250,this.Bl=914150663,this.Ch=-1856437926,this.Cl=812702999,this.Dh=355462360,this.Dl=-150054599,this.Eh=1731405415,this.El=-4191439,this.Fh=-1900787065,this.Fl=1750603025,this.Gh=-619958771,this.Gl=1694076839,this.Hh=1203062813,this.Hl=-1090891868,this.outputLen=48}}const X=d((()=>new R)),V=d((()=>new N)),Z=[],z=[],J=[],K=BigInt(0),Q=BigInt(1),W=BigInt(2),Y=BigInt(7),q=BigInt(256),tt=BigInt(113);for(let t=0,e=Q,s=1,i=0;t<24;t++){[s,i]=[i,(2*s+3*i)%5],Z.push(2*(5*i+s)),z.push((t+1)*(t+2)/2%64);let r=K;for(let t=0;t<7;t++)e=(e<<Q^(e>>Y)*tt)%q,e&W&&(r^=Q<<(Q<<BigInt(t))-Q);J.push(r)}const[et,st]=k(J,!0),it=(t,e,s)=>s>32?D(t,e,s):T(t,e,s),rt=(t,e,s)=>s>32?_(t,e,s):$(t,e,s);class nt extends u{keccak(){a||l(this.state32),function(t,e=24){const s=new Uint32Array(10);for(let i=24-e;i<24;i++){for(let e=0;e<10;e++)s[e]=t[e]^t[e+10]^t[e+20]^t[e+30]^t[e+40];for(let e=0;e<10;e+=2){const i=(e+8)%10,r=(e+2)%10,n=s[r],o=s[r+1],h=it(n,o,1)^s[i],a=rt(n,o,1)^s[i+1];for(let s=0;s<50;s+=10)t[e+s]^=h,t[e+s+1]^=a}let e=t[2],r=t[3];for(let s=0;s<24;s++){const i=z[s],n=it(e,r,i),o=rt(e,r,i),h=Z[s];e=t[h],r=t[h+1],t[h]=n,t[h+1]=o}for(let e=0;e<50;e+=10){for(let i=0;i<10;i++)s[i]=t[e+i];for(let i=0;i<10;i++)t[e+i]^=~s[(i+2)%10]&s[(i+4)%10]}t[0]^=et[i],t[1]^=st[i]}s.fill(0)}(this.state32,this.rounds),a||l(this.state32),this.posOut=0,this.pos=0}update(t){i(this);const{blockLen:e,state:s}=this,r=(t=c(t)).length;for(let i=0;i<r;){const n=Math.min(e-this.pos,r-i);for(let e=0;e<n;e++)s[this.pos++]^=t[i++];this.pos===e&&this.keccak()}return this}finish(){if(this.finished)return;this.finished=!0;const{state:t,suffix:e,pos:s,blockLen:i}=this;t[s]^=e,128&e&&s===i-1&&this.keccak(),t[i-1]^=128,this.keccak()}writeInto(t){i(this,!1),s(t),this.finish();const e=this.state,{blockLen:r}=this;for(let s=0,i=t.length;s<i;){this.posOut>=r&&this.keccak();const n=Math.min(r-this.posOut,i-s);t.set(e.subarray(this.posOut,this.posOut+n),s),this.posOut+=n,s+=n}return t}xofInto(t){
|
||||
if(!this.enableXOF)throw new Error("XOF is not possible for this instance");return this.writeInto(t)}xof(t){return e(t),this.xofInto(new Uint8Array(t))}digestInto(t){if(r(t,this),this.finished)throw new Error("digest() was already called");return this.writeInto(t),this.destroy(),t}digest(){return this.digestInto(new Uint8Array(this.outputLen))}destroy(){this.destroyed=!0,this.state.fill(0)}_cloneInto(t){const{blockLen:e,suffix:s,outputLen:i,rounds:r,enableXOF:n}=this;return t||(t=new nt(e,s,i,n,r)),t.state32.set(this.state32),t.pos=this.pos,t.posOut=this.posOut,t.finished=this.finished,t.rounds=r,t.suffix=s,t.outputLen=i,t.enableXOF=n,t.destroyed=this.destroyed,t}constructor(t,s,i,r=!1,n=24){if(super(),this.blockLen=t,this.suffix=s,this.outputLen=i,this.enableXOF=r,this.rounds=n,this.pos=0,this.posOut=0,this.finished=!1,this.destroyed=!1,e(i),0>=this.blockLen||this.blockLen>=200)throw new Error("Sha3 supports only keccak-f1600 function");var o;this.state=new Uint8Array(200),this.state32=(o=this.state,new Uint32Array(o.buffer,o.byteOffset,Math.floor(o.byteLength/4)))}}const ot=(t,e,s)=>d((()=>new nt(e,t,s))),ht=ot(6,144,28),at=ot(6,136,32),lt=ot(6,104,48),ct=ot(6,72,64),ut=(()=>{if("object"==typeof globalThis)return globalThis;Object.defineProperty(Object.prototype,"__GLOBALTHIS__",{get(){return this},configurable:!0});try{if("undefined"!=typeof __GLOBALTHIS__)return __GLOBALTHIS__}finally{delete Object.prototype.__GLOBALTHIS__}return"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:void 0})(),dt={SHA1:m,SHA224:U,SHA256:E,SHA384:V,SHA512:X,"SHA3-224":ht,"SHA3-256":at,"SHA3-384":lt,"SHA3-512":ct},ft=t=>{switch(!0){case/^(?:SHA-?1|SSL3-SHA1)$/i.test(t):return"SHA1";case/^SHA(?:2?-)?224$/i.test(t):return"SHA224";case/^SHA(?:2?-)?256$/i.test(t):return"SHA256";case/^SHA(?:2?-)?384$/i.test(t):return"SHA384";case/^SHA(?:2?-)?512$/i.test(t):return"SHA512";case/^SHA3-224$/i.test(t):return"SHA3-224";case/^SHA3-256$/i.test(t):return"SHA3-256";case/^SHA3-384$/i.test(t):
|
||||
return"SHA3-384";case/^SHA3-512$/i.test(t):return"SHA3-512";default:throw new TypeError(`Unknown hash algorithm: ${t}`)}},bt="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",gt=t=>{let e=(t=t.replace(/ /g,"")).length;for(;"="===t[e-1];)--e;t=(e<t.length?t.substring(0,e):t).toUpperCase();const s=new ArrayBuffer(5*t.length/8|0),i=new Uint8Array(s);let r=0,n=0,o=0;for(let e=0;e<t.length;e++){const s=bt.indexOf(t[e]);if(-1===s)throw new TypeError(`Invalid character found: ${t[e]}`);n=n<<5|s,r+=5,r>=8&&(r-=8,i[o++]=n>>>r)}return i},pt=t=>{let e=0,s=0,i="";for(let r=0;r<t.length;r++)for(s=s<<8|t[r],e+=8;e>=5;)i+=bt[s>>>e-5&31],e-=5;return e>0&&(i+=bt[s<<5-e&31]),i},wt=t=>{t=t.replace(/ /g,"");const e=new ArrayBuffer(t.length/2),s=new Uint8Array(e);for(let e=0;e<t.length;e+=2)s[e/2]=parseInt(t.substring(e,e+2),16);return s},yt=t=>{let e="";for(let s=0;s<t.length;s++){const i=t[s].toString(16);1===i.length&&(e+="0"),e+=i}return e.toUpperCase()},xt=t=>{const e=new ArrayBuffer(t.length),s=new Uint8Array(e);for(let e=0;e<t.length;e++)s[e]=255&t.charCodeAt(e);return s},At=t=>{let e="";for(let s=0;s<t.length;s++)e+=String.fromCharCode(t[s]);return e},mt=ut.TextEncoder?new ut.TextEncoder:null,Ht=ut.TextDecoder?new ut.TextDecoder:null,Lt=t=>{if(!mt)throw new Error("Encoding API not available");return mt.encode(t)},It=t=>{if(!Ht)throw new Error("Encoding API not available");return Ht.decode(t)};class St{static fromLatin1(t){return new St({buffer:xt(t).buffer})}static fromUTF8(t){return new St({buffer:Lt(t).buffer})}static fromBase32(t){return new St({buffer:gt(t).buffer})}static fromHex(t){return new St({buffer:wt(t).buffer})}get buffer(){return this.bytes.buffer}get latin1(){return Object.defineProperty(this,"latin1",{enumerable:!0,writable:!1,configurable:!1,value:At(this.bytes)}),this.latin1}get utf8(){return Object.defineProperty(this,"utf8",{enumerable:!0,writable:!1,configurable:!1,value:It(this.bytes)}),this.utf8}get base32(){return Object.defineProperty(this,"base32",{enumerable:!0,writable:!1,configurable:!1,value:pt(this.bytes)}),
|
||||
this.base32}get hex(){return Object.defineProperty(this,"hex",{enumerable:!0,writable:!1,configurable:!1,value:yt(this.bytes)}),this.hex}constructor({buffer:t,size:e=20}={}){this.bytes=void 0===t?(t=>{if(ut.crypto?.getRandomValues)return ut.crypto.getRandomValues(new Uint8Array(t));throw new Error("Cryptography API not available")})(e):new Uint8Array(t),Object.defineProperty(this,"bytes",{enumerable:!0,writable:!1,configurable:!1,value:this.bytes})}}class Bt{static get defaults(){return{issuer:"",label:"OTPAuth",issuerInLabel:!0,algorithm:"SHA1",digits:6,counter:0,window:1}}static generate({secret:t,algorithm:e=Bt.defaults.algorithm,digits:s=Bt.defaults.digits,counter:i=Bt.defaults.counter}){const r=((t,e,s)=>{if(b){const i=dt[t]??dt[ft(t)];return b(i,e,s)}throw new Error("Missing HMAC function")})(e,t.bytes,(t=>{const e=new ArrayBuffer(8),s=new Uint8Array(e);let i=t;for(let t=7;t>=0&&0!==i;t--)s[t]=255&i,i-=s[t],i/=256;return s})(i)),n=15&r[r.byteLength-1];return(((127&r[n])<<24|(255&r[n+1])<<16|(255&r[n+2])<<8|255&r[n+3])%10**s).toString().padStart(s,"0")}generate({counter:t=this.counter++}={}){return Bt.generate({secret:this.secret,algorithm:this.algorithm,digits:this.digits,counter:t})}static validate({token:t,secret:e,algorithm:s,digits:i=Bt.defaults.digits,counter:r=Bt.defaults.counter,window:n=Bt.defaults.window}){if(t.length!==i)return null;let o=null;const h=n=>{const h=Bt.generate({secret:e,algorithm:s,digits:i,counter:n});((t,e)=>{{if(t.length!==e.length)throw new TypeError("Input strings must have the same length");let s=-1,i=0;for(;++s<t.length;)i|=t.charCodeAt(s)^e.charCodeAt(s);return 0===i}})(t,h)&&(o=n-r)};h(r);for(let t=1;t<=n&&null===o&&(h(r-t),null===o)&&(h(r+t),null===o);++t);return o}validate({token:t,counter:e=this.counter,window:s}){return Bt.validate({token:t,secret:this.secret,algorithm:this.algorithm,digits:this.digits,counter:e,window:s})}toString(){const t=encodeURIComponent
|
||||
;return"otpauth://hotp/"+(this.issuer.length>0?this.issuerInLabel?`${t(this.issuer)}:${t(this.label)}?issuer=${t(this.issuer)}&`:`${t(this.label)}?issuer=${t(this.issuer)}&`:`${t(this.label)}?`)+`secret=${t(this.secret.base32)}&`+`algorithm=${t(this.algorithm)}&`+`digits=${t(this.digits)}&`+`counter=${t(this.counter)}`}constructor({issuer:t=Bt.defaults.issuer,label:e=Bt.defaults.label,issuerInLabel:s=Bt.defaults.issuerInLabel,secret:i=new St,algorithm:r=Bt.defaults.algorithm,digits:n=Bt.defaults.digits,counter:o=Bt.defaults.counter}={}){this.issuer=t,this.label=e,this.issuerInLabel=s,this.secret="string"==typeof i?St.fromBase32(i):i,this.algorithm=ft(r),this.digits=n,this.counter=o}}class Et{static get defaults(){return{issuer:"",label:"OTPAuth",issuerInLabel:!0,algorithm:"SHA1",digits:6,period:30,window:1}}static counter({period:t=Et.defaults.period,timestamp:e=Date.now()}={}){return Math.floor(e/1e3/t)}counter({timestamp:t=Date.now()}={}){return Et.counter({period:this.period,timestamp:t})}static remaining({period:t=Et.defaults.period,timestamp:e=Date.now()}={}){return 1e3*t-e%(1e3*t)}remaining({timestamp:t=Date.now()}={}){return Et.remaining({period:this.period,timestamp:t})}static generate({secret:t,algorithm:e,digits:s,period:i=Et.defaults.period,timestamp:r=Date.now()}){return Bt.generate({secret:t,algorithm:e,digits:s,counter:Et.counter({period:i,timestamp:r})})}generate({timestamp:t=Date.now()}={}){return Et.generate({secret:this.secret,algorithm:this.algorithm,digits:this.digits,period:this.period,timestamp:t})}static validate({token:t,secret:e,algorithm:s,digits:i,period:r=Et.defaults.period,timestamp:n=Date.now(),window:o}){return Bt.validate({token:t,secret:e,algorithm:s,digits:i,counter:Et.counter({period:r,timestamp:n}),window:o})}validate({token:t,timestamp:e,window:s}){return Et.validate({token:t,secret:this.secret,algorithm:this.algorithm,digits:this.digits,period:this.period,timestamp:e,window:s})}toString(){const t=encodeURIComponent
|
||||
;return"otpauth://totp/"+(this.issuer.length>0?this.issuerInLabel?`${t(this.issuer)}:${t(this.label)}?issuer=${t(this.issuer)}&`:`${t(this.label)}?issuer=${t(this.issuer)}&`:`${t(this.label)}?`)+`secret=${t(this.secret.base32)}&`+`algorithm=${t(this.algorithm)}&`+`digits=${t(this.digits)}&`+`period=${t(this.period)}`}constructor({issuer:t=Et.defaults.issuer,label:e=Et.defaults.label,issuerInLabel:s=Et.defaults.issuerInLabel,secret:i=new St,algorithm:r=Et.defaults.algorithm,digits:n=Et.defaults.digits,period:o=Et.defaults.period}={}){this.issuer=t,this.label=e,this.issuerInLabel=s,this.secret="string"==typeof i?St.fromBase32(i):i,this.algorithm=ft(r),this.digits=n,this.period=o}}const Ut=/^otpauth:\/\/([ht]otp)\/(.+)\?([A-Z0-9.~_-]+=[^?&]*(?:&[A-Z0-9.~_-]+=[^?&]*)*)$/i,Ct=/^[2-7A-Z]+=*$/i,Ot=/^SHA(?:1|224|256|384|512|3-224|3-256|3-384|3-512)$/i,vt=/^[+-]?\d+$/,kt=/^\+?[1-9]\d*$/;t.HOTP=Bt,t.Secret=St,t.TOTP=Et,t.URI=class{static parse(t){let e;try{e=t.match(Ut)}catch(t){}if(!Array.isArray(e))throw new URIError("Invalid URI format");const s=e[1].toLowerCase(),i=e[2].split(/(?::|%3A) *(.+)/i,2).map(decodeURIComponent),r=e[3].split("&").reduce(((t,e)=>{const s=e.split(/=(.*)/,2).map(decodeURIComponent),i=s[0].toLowerCase(),r=s[1],n=t;return n[i]=r,n}),{});let n;const o={};if("hotp"===s){if(n=Bt,void 0===r.counter||!vt.test(r.counter))throw new TypeError("Missing or invalid 'counter' parameter");o.counter=parseInt(r.counter,10)}else{if("totp"!==s)throw new TypeError("Unknown OTP type");if(n=Et,void 0!==r.period){if(!kt.test(r.period))throw new TypeError("Invalid 'period' parameter");o.period=parseInt(r.period,10)}}if(void 0!==r.issuer&&(o.issuer=r.issuer),2===i.length?(o.label=i[1],void 0===o.issuer||""===o.issuer?o.issuer=i[0]:""===i[0]&&(o.issuerInLabel=!1)):(o.label=i[0],void 0!==o.issuer&&""!==o.issuer&&(o.issuerInLabel=!1)),void 0===r.secret||!Ct.test(r.secret))throw new TypeError("Missing or invalid 'secret' parameter");if(o.secret=r.secret,void 0!==r.algorithm){
|
||||
if(!Ot.test(r.algorithm))throw new TypeError("Invalid 'algorithm' parameter");o.algorithm=r.algorithm}if(void 0!==r.digits){if(!kt.test(r.digits))throw new TypeError("Invalid 'digits' parameter");o.digits=parseInt(r.digits,10)}return new n(o)}static stringify(t){if(t instanceof Bt||t instanceof Et)return t.toString();throw new TypeError("Invalid 'HOTP/TOTP' object")}},t.version="9.4.0"}));
|
||||
//# sourceMappingURL=otpauth.umd.min.js.map
|
||||
@@ -1,5 +0,0 @@
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./vue.common.prod.js')
|
||||
} else {
|
||||
module.exports = require('./vue.common.dev.js')
|
||||
}
|
||||
6
web/assets/vue/vue.esm.browser.min.js
vendored
11932
web/assets/vue/vue.js
@@ -1,5 +0,0 @@
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./vue.runtime.common.prod.js')
|
||||
} else {
|
||||
module.exports = require('./vue.runtime.common.dev.js')
|
||||
}
|
||||