Compare commits

..

245 Commits

Author SHA1 Message Date
itdoginfo
21fa017443 Merge pull request #114 from itdoginfo/fix/comments
Fix/comments
2025-05-21 15:43:58 +03:00
Ivan K
f1954df83b ♻️ refactor(diagnosticTab): move command execution helpers to utils.js 2025-05-21 15:09:42 +03:00
Ivan K
8573bd99b5 ♻️ refactor(diagnosticTab): remove unused debug 2025-05-21 14:21:37 +03:00
Ivan K
c3f44bd124 fix(diagnosticTab): add error polling and notification system 2025-05-21 14:18:23 +03:00
itdoginfo
59e394c4f2 v0.4.1 JS refactoring 2025-05-21 14:16:16 +03:00
itdoginfo
c897c90371 Merge pull request #110 from itdoginfo/fix/comments
♻️ refactor(podkop): modularize configuration and diagnostics sections
2025-05-21 13:48:30 +03:00
Ivan K
bcab66f88c ♻️ refactor(podkop): enhance check_nft function for domain-specific set statistics 2025-05-21 09:48:53 +03:00
Ivan K
05a551e5e3 💄 style(podkop): remove extra newline in NFT check completed message 2025-05-20 19:28:40 +03:00
Ivan K
1f81ec8403 🔧 chore(podkop): use check_nft in global_check 2025-05-20 17:32:04 +03:00
Ivan K
9748178562 ♻️ refactor(podkop): enhance nft set statistics and chain configurations 2025-05-20 17:28:00 +03:00
Ivan K
1411e7d403 ♻️ refactor(diagnosticTab): improve command execution and UI updates 2025-05-19 20:32:08 +03:00
Ivan K
d81a90bd28 ♻️ refactor(diagnosticTab): improve status updates and caching 2025-05-19 19:59:29 +03:00
Ivan K
82f4720326 ♻️ refactor(podkop): rename sections into correct files 2025-05-18 15:58:59 +03:00
Ivan K
10f246ea61 ♻️ refactor(podkop): move URL validation to config.js 2025-05-16 23:30:23 +03:00
Ivan K
c0571320f1 ♻️ refactor(networkUtils): remove custom network functions 2025-05-16 22:22:07 +03:00
Ivan K
a658ca5518 💄 style(podkop): remove unused networkUtils import 2025-05-16 18:40:29 +03:00
Ivan K
08709c93c7 ♻️ refactor(podkop): rename section variables for clarity 2025-05-16 18:28:06 +03:00
Ivan K
cf5b2216be ♻️ refactor(podkop): reorganize sections into subdirectory 2025-05-16 18:23:16 +03:00
Ivan K
682913ade0 ♻️ refactor(podkop): remove unused parameter from createAdditionalSection 2025-05-16 18:08:29 +03:00
Ivan K
3b2cbd0332 ♻️ refactor(podkop): modularize configuration and diagnostics sections 2025-05-16 18:04:33 +03:00
itdoginfo
8f9dcf2c55 Merge pull request #109 from itdoginfo/fix/comments
♻️ refactor(podkop): refactor domain list and API endpoints
2025-05-16 14:50:59 +03:00
Ivan K
91d027b5fe ♻️ refactor(podkop): refactor domain list and API endpoints 2025-05-16 14:46:06 +03:00
itdoginfo
f90ab7f468 v0.4.0 beta 2025-05-15 13:26:57 +03:00
itdoginfo
e4bfd447ce Merge pull request #108 from itdoginfo/fix/comments
fix(podkop): add dont touch my dhcp logic to fake IP check functions
2025-05-15 12:07:08 +03:00
Ivan K
fbdd759b83 ♻️ refactor(podkop): remove redundant DNS/sing-box checks in fakeip status 2025-05-15 11:53:30 +03:00
Ivan K
2488bc30b1 fix(podkop): add dont touch my dhcp logic to fake IP check functions 2025-05-15 11:31:33 +03:00
itdoginfo
dcc12cf920 Merge pull request #107 from itdoginfo/fix/comments
💄 style(podkop): update modal button titles and clipboard content
2025-05-14 14:33:53 +03:00
Ivan K
c99cef9f27 💄 style(podkop): update modal button titles and clipboard content 2025-05-14 14:30:52 +03:00
itdoginfo
8a68f3fcc2 Update 2025-05-13 14:26:50 +03:00
itdoginfo
ed2994be3a v0.3.50 Adde Google AI, Play, HTZ, OVH of list 2025-05-12 23:31:06 +03:00
itdoginfo
77ff5ab781 Merge pull request #105 from itdoginfo/fix/comments
️ perf(dns): improve DNS query performance and error handling
2025-05-12 18:17:50 +03:00
Ivan K
1c80bc5a5e ️ perf(dns): improve DNS query performance and error handling 2025-05-12 17:50:50 +03:00
itdoginfo
f688d74c32 v0.3.49 Improved diagnostics 2025-05-12 17:32:51 +03:00
itdoginfo
7bc50d58d3 Merge pull request #104 from itdoginfo/fix/comments
Fix/comments
2025-05-12 17:25:59 +03:00
Ivan K
77ce0c380b 🐛 fix(dns): improve DNS availability check logic 2025-05-12 17:21:38 +03:00
Ivan K
47d1b349c7 ♻️ refactor(podkop): add local var 2025-05-12 16:51:59 +03:00
Ivan K
e9face1f4a ♻️ refactor(podkop): simplify logging function 2025-05-12 16:48:59 +03:00
itdoginfo
e5bf7d9bed Merge pull request #103 from itdoginfo/fix/comments
♻️ refactor(podkop): improve diagnostics and error handling
2025-05-12 16:48:15 +03:00
Ivan K
dd4722f3e1 ♻️ refactor(podkop): simplify logging functions 2025-05-12 16:40:44 +03:00
Ivan K
1e945dafe7 feat(logging): add colored logging to stdout and syslog 2025-05-12 15:54:12 +03:00
itdoginfo
b080521a58 Update 2025-05-12 00:57:52 +03:00
itdoginfo
6a96a85773 Update 2025-05-12 00:49:19 +03:00
itdoginfo
6fb3a36974 Update 2025-05-12 00:46:30 +03:00
Ivan K
b3dbee1dbe 💄 style(podkop): adjust margin styles in status panel 2025-05-11 20:30:16 +03:00
Ivan K
916321578d ️ feat(dns): add random DNS query ID generation 2025-05-11 19:33:08 +03:00
Ivan K
c74d733717 ♻️ refactor(podkop): improve diagnostics and error handling 2025-05-11 19:26:29 +03:00
itdoginfo
433724f762 v0.3.48 Custom URL CRLF 2025-05-10 18:55:35 +03:00
itdoginfo
6378aa9910 Update 2025-05-10 16:15:53 +03:00
itdoginfo
68f5f123ca v0.3.47 Fix noresolv 1 2025-05-10 12:50:01 +03:00
itdoginfo
fae43d0471 v0.3.46 2025-05-08 19:24:08 +03:00
itdoginfo
9d6dc45fdb #99 Block mode 2025-05-08 19:23:45 +03:00
itdoginfo
9aa5a2d242 Fix site. #100 added ntpd 2025-05-08 10:14:58 +03:00
itdoginfo
63dc86fca4 v0.3.45 Update checker domain 2025-05-07 22:39:23 +03:00
itdoginfo
4d9cedaf4c Return upgrade command 2025-05-07 17:58:19 +03:00
itdoginfo
14e7cbae01 v0.3.44 2025-05-07 17:26:57 +03:00
itdoginfo
c9f610bb1e Change to ip.podkop.net. Fix log. Added restart 2025-05-07 17:24:32 +03:00
itdoginfo
19671c7f67 Stop btn. Change to ip.podkop.net 2025-05-07 17:23:20 +03:00
itdoginfo
6d1e4091e5 Detour 2025-05-07 00:22:04 +03:00
itdoginfo
96d661c49f Fixed default values ttl in comment 2025-05-03 18:52:57 +03:00
itdoginfo
da8dd06b34 Move doc to wiki 2025-05-03 18:12:00 +03:00
itdoginfo
2c1bcffb6d fix iptables 2025-05-03 18:11:49 +03:00
itdoginfo
3040ce7286 v0.3.43 2025-05-02 14:53:11 +03:00
itdoginfo
e025271a14 Added to global check: DNS check and proxy check. From VizzleTF 2025-05-02 14:50:06 +03:00
itdoginfo
2b8208186d Fix global check text 2025-05-02 13:55:07 +03:00
itdoginfo
17fb11baf0 Fixed diagnostics from VizzleTF 2025-05-02 13:34:11 +03:00
itdoginfo
3c1b041b52 Edited text from #96 2025-05-01 22:52:41 +03:00
itdoginfo
38acac1a31 Merge pull request #96 from itdoginfo/chore/sing-box-status
Issue #91 , Issue #94
2025-05-01 22:16:38 +03:00
Ivan K
2939229df3 back to the future 2025-05-01 19:30:05 +03:00
Ivan K
26c3d0bc7e ♻️ refactor(podkop): simplify DoH URL determination logic 2025-05-01 19:26:21 +03:00
Ivan K
b364363b1b feat(dns): add DoH URL resolution function 2025-05-01 19:20:36 +03:00
itdoginfo
d85caf0c0c Fix https-dns-proxy i18 2025-05-01 19:10:18 +03:00
Ivan K
65f72e1e04 ♻️ refactor(podkop): update WARP detection logic 2025-05-01 18:29:42 +03:00
Ivan K
e59ef6dd6f 💄 style(podkop): remove unnecessary sed commands in global_check 2025-05-01 17:57:51 +03:00
Ivan K
05272de650 💄 style(podkop): update formatting and messages 2025-05-01 17:48:25 +03:00
Ivan K
48716e7156 Enhance Podkop functionality with global check feature and improved diagnostics. Added support for FakeIP tests in both browser and router contexts. Updated UI elements for better status reporting and added localization for new messages. 2025-05-01 17:18:07 +03:00
itdoginfo
f29b97e495 v0.3.42 2025-05-01 14:17:18 +03:00
itdoginfo
41c21cebcd Fixed validation for ws 2025-04-30 23:43:36 +03:00
itdoginfo
238e99a547 Update 2025-04-30 19:02:31 +03:00
itdoginfo
4f44fcfe99 Update 2025-04-30 14:48:12 +03:00
itdoginfo
9fd2fb9b6e Update 2025-04-30 00:19:42 +03:00
itdoginfo
c0591b25b9 Fix 2025-04-30 00:16:09 +03:00
itdoginfo
97fd392334 Fixed read. Added upgrade flag 2025-04-30 00:11:55 +03:00
itdoginfo
848c784cc0 Fix 2025-04-29 23:49:28 +03:00
itdoginfo
ab971dcd36 Update 2025-04-29 23:48:49 +03:00
itdoginfo
b8d96f28cd Added CF. Fixed https-dns-proxy warning. Masked for static wan 2025-04-29 18:54:50 +03:00
itdoginfo
f2268fd494 v0.3.41. Improved Diagnotics: WAN, WARP, versions, etc 2025-04-29 12:53:29 +03:00
itdoginfo
19897afcdd v0.3.40. Improved Diagnotics 2025-04-28 00:33:07 +03:00
itdoginfo
0e2ea60f01 v0.3.39. Added global check button 2025-04-27 19:29:34 +03:00
itdoginfo
2dc5944961 Fix https-dns-proxy --force-depends 2025-04-27 18:07:58 +03:00
itdoginfo
f65de36804 Detect https-dns-proxy 2025-04-27 15:50:37 +03:00
itdoginfo
19541f8bb3 v0.3.38. fix reload config luci 2025-04-26 22:35:11 +03:00
itdoginfo
aa42c707fe v0.3.37 2025-04-26 17:49:28 +03:00
itdoginfo
bf96f93987 Fix kill stderr. Return if 127.0.0.42 exists 2025-04-26 17:49:04 +03:00
itdoginfo
ff9aad8947 Option enable iface mon 2025-04-26 17:47:52 +03:00
itdoginfo
d9718617bd Option enable iface mon 2025-04-26 17:47:42 +03:00
itdoginfo
e865c9f324 Validate raw network. Path for DoH. Bool for iface monitoring 2025-04-26 17:47:08 +03:00
itdoginfo
7df8bb5826 rmempty proxy url string 2025-04-25 19:29:31 +03:00
itdoginfo
f960358eb6 0.3.36 2025-04-25 10:57:59 +03:00
itdoginfo
ba44966c02 Interface trigger. Disable sing-box autostart. dont touch dhcp. reload without dnsmasq restart 2025-04-24 19:25:08 +03:00
itdoginfo
615241aa37 Merge pull request #88 from Davoyan/patch-1
Update localisation
2025-04-22 11:36:38 +03:00
Davoyan
9a3220d226 Update localisation 2025-04-22 11:24:54 +03:00
itdoginfo
ec8d28857e #82 and #83 2025-04-15 00:42:16 +03:00
itdoginfo
26b49f5bbb Check fix 2025-04-15 00:15:28 +03:00
itdoginfo
0a7efb3169 Fix 2025-04-03 17:53:21 +03:00
itdoginfo
468e51ee8e v0.3.35 2025-04-03 17:42:45 +03:00
itdoginfo
3b93a914de v0.3.34 2025-04-03 17:27:35 +03:00
itdoginfo
76c5baf1e2 Fix tailscale smartdns in resolve.conf 2025-04-03 17:27:13 +03:00
itdoginfo
c752c46abf Fix resolv_conf value 2025-04-03 17:24:57 +03:00
itdoginfo
1df1defa5e Check curl 2025-04-03 17:24:31 +03:00
itdoginfo
3cb4be6427 v0.3.33 2025-04-03 16:47:47 +03:00
itdoginfo
25bfdce5ce Added critical log. Rm friendlywrt check. Added iptables check 2025-04-03 16:47:26 +03:00
itdoginfo
6d0f097a07 Merge pull request #75 from itdoginfo/feature/error-notification (#47)
Feature/error notification
2025-04-03 14:52:00 +03:00
Ivan K
5f780955eb 💄 style(podkop): update error log filtering criteria 2025-04-03 13:42:55 +03:00
Ivan K
389def9056 ♻️ refactor(podkop): remove unused createErrorModal function 2025-04-03 13:40:41 +03:00
Ivan K
e816da5133 feat(podkop): add error logging and notification system 2025-04-03 13:36:22 +03:00
Ivan K
e57adbe042 🔒 refactor(config): Mask NextDNS server address in config output 2025-03-30 20:36:49 +03:00
itdoginfo
d78c51360d Merge pull request #73 from itdoginfo/feature/no-more-cache
🐛 fix(doh): Improve DoH server compatibility detection for quad9
2025-03-30 18:44:26 +03:00
Ivan K
c2357337fc 🐛 fix(dns): improve DoH server compatibility and error handling 2025-03-30 17:20:06 +03:00
Ivan K
bc6490b56e 🐛 fix(doh): Improve DoH server compatibility detection for quad9 2025-03-30 17:04:30 +03:00
itdoginfo
2f645d9151 v0.3.32 2025-03-30 16:03:49 +03:00
itdoginfo
94cc65001b Merge pull request #72 from itdoginfo/feature/no-more-cache
🐛 fix(podkop): Handle DNS check errors and timeouts properly
2025-03-30 16:02:44 +03:00
Ivan K
87caa70e97 feat(dns): Mask NextDNS ID in DNS availability check output 2025-03-30 14:53:01 +03:00
Ivan K
90d7c60fcb 🐛 fix(podkop): Handle DNS check errors and timeouts properly 2025-03-30 14:46:03 +03:00
itdoginfo
3f114b4710 v0.3.31 2025-03-30 12:41:40 +03:00
itdoginfo
b821abe82c Merge pull request #67 from itdoginfo/feature/no-more-cache
Feature/add DNS and bypass status checks to diagnostics
2025-03-30 12:37:58 +03:00
Ivan K
732cab2ef3 🐛 fix(podkop): fix typo in translation and dns check timeout 2025-03-30 09:54:31 +03:00
Ivan K
3b4ce9e7a3 feat: add visibility change event listener for diagnostics updates 2025-03-21 15:06:08 +03:00
Ivan K
69c4445c85 refactor: move buttons for NFT and DNSMasq checks 2025-03-21 14:54:52 +03:00
Ivan K
dcebc3d67d docs: update Russian translations for proxy configuration and add new translation strings 2025-03-21 14:53:24 +03:00
Ivan K
1be31eaf59 feat: add local DNS availability check and display in UI 2025-03-21 14:47:20 +03:00
Ivan K
023210e0f0 style: update text for Bypass Status to Main config 2025-03-21 14:21:35 +03:00
Ivan K
5ff832533e feat: add DNS and bypass status checks to diagnostics 2025-03-21 13:03:29 +03:00
Ivan K
5d2163515e refactor: improve caching prevention logic 2025-03-20 21:47:55 +03:00
Ivan K
5865706d0c feat: add timestamp to URL to prevent caching 2025-03-20 21:45:08 +03:00
itdoginfo
aabe1c53dc Update 2025-03-18 00:32:06 +03:00
itdoginfo
8e91b582ad #36 2025-03-18 00:31:56 +03:00
itdoginfo
62ce1f5acc v0.3.30 2025-03-17 14:44:07 +03:00
itdoginfo
93727ddeb5 Processing empty values 2025-03-17 14:43:33 +03:00
itdoginfo
98797d93b1 v0.3.29 2025-03-17 13:16:38 +03:00
itdoginfo
66c6e998a2 #38 #46 2025-03-17 13:14:37 +03:00
itdoginfo
3d9f82b571 Merge pull request #65 from itdoginfo/chore/fakeip-method
feat: add diagnostics functionality only in tab
2025-03-14 12:33:52 +03:00
Ivan K
38d082e236 feat: add diagnostics functionality only in tab 2025-03-14 09:50:28 +03:00
itdoginfo
9f5abcae6d v0.3.28 2025-03-13 19:34:52 +03:00
itdoginfo
7836d2c6ec Fix 2025-03-13 19:32:57 +03:00
itdoginfo
f46c934c59 Test 2025-03-13 19:30:17 +03:00
itdoginfo
23ed10d393 Added check version in Makefile 2025-03-13 19:28:31 +03:00
itdoginfo
26488baad3 Merge pull request #64 from itdoginfo/chore/fakeip-method
fix: fix enable/disable functionality to podkop service
2025-03-13 19:05:49 +03:00
Ivan K
c79016e456 feat: add createInitActionButton function to ButtonFactory 2025-03-13 10:35:48 +03:00
Ivan K
884bbfee42 fix: remove unused button creation code 2025-03-13 10:32:34 +03:00
Ivan K
1263b9b1b8 fix: fix enable/disable functionality to podkop service 2025-03-13 10:00:37 +03:00
Ivan K
23203fd7a1 feat: add createSystemButton function to ButtonFactory and flush cache button 2025-03-13 00:10:18 +03:00
itdoginfo
25c887a952 v0.3.27 2025-03-12 17:20:35 +03:00
itdoginfo
e7a3c7adf1 Merge pull request #63 from itdoginfo/chore/fakeip-method
feat: update DNS checks and improve FakeIP status reporting
2025-03-12 17:02:18 +03:00
Ivan K
3e96b9a1af feat: update DNS checks and improve FakeIP status reporting 2025-03-12 16:20:59 +03:00
itdoginfo
251f94cb88 v0.3.26 2025-03-12 14:59:03 +03:00
itdoginfo
44936c698e Merge pull request #62 from itdoginfo/chore/fakeip-method
feat: add CLI check for FakeIP functionality and update status display
2025-03-12 14:57:41 +03:00
Ivan K
0faaca12fc сhore: remove tabs 2025-03-12 14:54:56 +03:00
Ivan K
c6d1f05916 feat: add CLI check for FakeIP functionality and update status display 2025-03-11 19:14:21 +03:00
itdoginfo
57554d518b v0.3.25 2025-03-11 18:39:30 +03:00
itdoginfo
09d761956c Some fixes 2025-03-11 18:39:18 +03:00
itdoginfo
ada807fec3 v0.3.24 2025-03-07 14:46:45 +03:00
itdoginfo
b28a5f1293 New default TTL=60, DOH=8.8.8.8 2025-03-07 14:46:22 +03:00
itdoginfo
2332eae5ff Added dns and github checker. JSON file for custom URL lists 2025-03-07 14:45:36 +03:00
itdoginfo
a755b6661d Merge pull request #59 from itdoginfo/feat/multiple-mixed-inbounds
Add support for multiple mixed inbounds with unique ports
2025-03-07 13:10:32 +03:00
Nikita Skryabin
567ce52253 feat: add support for multiple mixed inbounds with unique ports 2025-03-06 22:54:25 +03:00
Nikita Skryabin
b736360b66 fix: ensure routing rule for mixed-in is always applied 2025-03-06 21:55:40 +03:00
itdoginfo
3b2a7ba8af Create /usr/bin/podkop 2025-03-05 01:08:30 +03:00
itdoginfo
c96de62d96 v0.3.22 2025-03-04 13:36:43 +03:00
itdoginfo
14b7fbe4f7 Fix cidr for all_traffic+exclude 2025-03-04 13:36:20 +03:00
itdoginfo
3d05fe8be4 0.3.21 2025-03-03 21:28:21 +03:00
itdoginfo
6ddf9d3b24 Fix section for all_traffic_ip 2025-03-03 21:28:12 +03:00
itdoginfo
b401243f74 0.3.20 2025-03-03 18:26:19 +03:00
itdoginfo
407ef404ac Fix ip_cidr+fakeip, all_traffic_from_ip_enabled list 2025-03-03 18:26:02 +03:00
itdoginfo
f2e45bbbb9 Fix default value 2025-03-03 11:21:49 +03:00
itdoginfo
c2b37a14f4 v0.3.19 2025-02-26 18:24:40 +03:00
itdoginfo
3d029edaea Update 2025-02-26 18:23:02 +03:00
itdoginfo
b86d6d6294 Merge pull request #52 from itdoginfo/fix/increase-timeout-safeexec
feat: add support for comments in proxy and domain/subnet configuration
2025-02-26 18:18:43 +03:00
Ivan K
5c48ead9e4 feat: add support for comments in proxy and domain/subnet configuration 2025-02-24 23:02:23 +03:00
Ivan K
53475b5e8a fix: increase timeout for safeExec function 2025-02-24 20:07:47 +03:00
Ivan K
59e1d75870 refactor: increase timeout for safeExec function 2025-02-24 19:37:59 +03:00
itdoginfo
3ec6cc4d84 0.3.18 2025-02-24 18:07:15 +03:00
itdoginfo
3413af9f94 Merge pull request #51 from itdoginfo/fix/vpn-devices
feat: add section_id parameter to getNetworkInterfaces function
2025-02-24 17:42:30 +03:00
Ivan K
76b5ceae5c feat: add section_id parameter to getNetworkInterfaces function 2025-02-24 17:39:56 +03:00
itdoginfo
99ccd9fbb3 0.3.17 2025-02-24 16:42:35 +03:00
itdoginfo
b82c6eb718 Merge pull request #50 from itdoginfo/fix/many-sni-support
feat: update network interface loading in podkop.js
2025-02-24 16:24:53 +03:00
Ivan K
ccc87d9aa0 feat: update network interface loading in podkop.js 2025-02-24 16:23:05 +03:00
itdoginfo
8bcdee87f5 0.3.16 2025-02-24 15:39:02 +03:00
itdoginfo
f77ef5626b default dns options 2025-02-24 15:38:50 +03:00
itdoginfo
b50a21ded7 rm wget_github 2025-02-24 15:38:24 +03:00
itdoginfo
a831054e5e Merge pull request #48 from itdoginfo/fix/many-sni-support
feat: add status panels and utility functions for better diagnostics UI
2025-02-24 10:05:05 +03:00
Ivan K
a8dbff816c fix: correct logic for checking fakeipStatus state 2025-02-24 10:00:51 +03:00
Ivan K
171381fa18 refactor: improve error handling and code readability in podkop.js and update init.d script to check sing-box status 2025-02-23 22:56:01 +03:00
Ivan K
b806586a5a fix: проверки диагностики только при активной вкладке 2025-02-23 18:13:41 +03:00
Ivan K
9e2b192181 feat: add status panels and utility functions for better diagnostics UI 2025-02-23 13:35:30 +03:00
itdoginfo
c5be041664 Update 2025-02-23 00:11:52 +03:00
itdoginfo
445ad6d3d2 Update todo 2025-02-22 17:31:17 +03:00
itdoginfo
9203315107 Merge pull request #42 from itdoginfo/fix/many-sni-support
fix: add missing URL decoding for semicolon
2025-02-22 14:52:03 +03:00
Ivan K
d8d8d79d68 feat: add support for outbound JSON configuration in sing-box 2025-02-22 14:15:27 +03:00
Ivan K
615928db4e Merge remote-tracking branch 'origin/main' into fix/many-sni-support 2025-02-22 12:49:59 +03:00
Ivan K
7697754a73 refactor: replace fs.exec with safeExec for command execution with timeout 2025-02-22 12:45:25 +03:00
Ivan K
25107a0481 refactor: simplify label fetching and decoding in podkop.js 2025-02-22 09:52:04 +03:00
itdoginfo
5f5b1cbe1f Warnning for friendlywrt, http-dns-proxy. Validation domains in local file 2025-02-22 00:04:24 +03:00
Ivan K
a278918e77 feat: add timeout and chunking to proxy label fetching 2025-02-21 17:55:39 +03:00
itdoginfo
2074ccecce 0.3.15 2025-02-21 17:41:35 +03:00
itdoginfo
06f9bee038 #42 2025-02-21 17:40:52 +03:00
Ivan K
febb69d0be fix: add enable/disable button for Podkop service 2025-02-21 17:38:57 +03:00
Ivan K
1a6ee45612 fix: add function to dynamically fetch network interfaces for VPN configuration 2025-02-21 17:34:31 +03:00
itdoginfo
891b8f713d Fix 2025-02-21 16:07:27 +03:00
itdoginfo
b96552fb49 Fix #41 2025-02-21 16:06:17 +03:00
itdoginfo
ce9a7cdc45 Fix \n 2025-02-21 15:40:22 +03:00
itdoginfo
6071a96e9c 0.3.14 2025-02-21 15:37:43 +03:00
Ivan K
000d2f8e18 fix: add missing URL decoding for semicolon 2025-02-21 15:37:30 +03:00
itdoginfo
e17422a0cf Fix #37 #41 2025-02-21 15:37:20 +03:00
itdoginfo
2e78b2b4b8 Merge pull request #41 from itdoginfo/refactor/deduplicate-sections
Refactor/deduplicate sections
2025-02-21 14:55:03 +03:00
Ivan K
b84f3b6782 feat: add get_proxy_label function to podkop init script 2025-02-21 12:01:46 +03:00
itdoginfo
0f66305e50 Fix 2025-02-21 11:53:08 +03:00
Ivan K
a32a5c600b fix: update domain validation regex to allow single-level domains 2025-02-21 11:47:14 +03:00
Ivan K
89737efcbc refactor: refactor checkFakeIP to return a promise and update updateDiagnostics to use async/await 2025-02-21 11:22:37 +03:00
Ivan K
4608bc31cd refactor: update podkop.js to modularize configuration sections and improve validation logic 2025-02-21 11:09:47 +03:00
itdoginfo
d9e9f2dfe4 Update 2025-02-21 00:50:53 +03:00
Nikita Skryabin
bb9318e96f Merge pull request #37 from vernette/feature/fakeip-cache-path-and-ttl
feat(podkop): add configurable cache file path and dns rewrite_ttl options
2025-02-21 00:20:15 +03:00
Nikita Skryabin
7ff49c3e4e chore(init.d/podkop): remove unused cache file path and constant 2025-02-21 00:17:43 +03:00
Nikita Skryabin
134a79cb3b refactor(podkop.js): remove redundant path validation logic 2025-02-20 23:56:20 +03:00
Nikita Skryabin
560dda8604 feat(podkop): add translations for cache file and rewrite ttl options 2025-02-20 23:49:41 +03:00
Nikita Skryabin
255c08a6de feat(podkop.js): add validation for cache file path to ensure it meets specific criteria 2025-02-20 23:44:32 +03:00
Nikita Skryabin
1f3a65347e feat(podkop): add DNS Rewrite TTL configuration option 2025-02-20 23:27:50 +03:00
Nikita Skryabin
ec936e2369 feat(podkop): add configurable cache file path support 2025-02-20 22:49:58 +03:00
itdoginfo
cee934d139 Merge pull request #34 from itdoginfo/feature/fakeip-updater
feat: enhance FakeIP status check with periodic updates
2025-02-20 21:59:04 +03:00
Ivan K
a25c6b8013 feat: enhance FakeIP status check with periodic updates 2025-02-20 20:28:51 +03:00
itdoginfo
ec3a281cef v0.3.13 2025-02-20 17:22:15 +03:00
itdoginfo
86947e7dee Fix dns_server value 2025-02-20 17:22:03 +03:00
itdoginfo
ff5d017acc Update and rm install 0.2.5 2025-02-20 16:50:06 +03:00
itdoginfo
22d919657c Merge remote-tracking branch 'origin/main' 2025-02-20 16:48:20 +03:00
itdoginfo
3271f23ae0 Fix noresolv bakup 2025-02-20 16:45:22 +03:00
itdoginfo
35ea1a14cf Merge pull request #33 from vernette/feature/dns-server-selection
feat(podkop): add DNS server and protocol selection options
2025-02-20 16:39:05 +03:00
unknown
51a9cc5934 feat(podkop.po): add translations for DNS server address validation messages 2025-02-20 16:34:55 +03:00
unknown
e1df26e62b feat(podkop.js): add DNS server validation for IP and domain formats 2025-02-20 16:33:23 +03:00
unknown
75b8bef0e0 fix(podkop.js): update DNS protocol type and server labels to use translation function 2025-02-20 16:07:38 +03:00
unknown
1a6b0cac46 chore(init.d/podkop): remove redundant comments 2025-02-20 16:03:15 +03:00
unknown
e49bd91109 feat(podkop.po): add translations for DNS protocol and server options 2025-02-20 16:01:23 +03:00
unknown
85642a2585 feat(podkop.pot): add new DNS protocol and server options for translation 2025-02-20 16:01:12 +03:00
unknown
c31785d20e feat(init.d/podkop): add DNS resolver discovery and dynamic configuration 2025-02-20 15:57:52 +03:00
unknown
a0af04037a feat(podkop.js): add DNS protocol type and server options to configuration 2025-02-20 15:57:18 +03:00
itdoginfo
51fb10e30e fix 2025-02-20 00:43:39 +03:00
itdoginfo
069ea41ef8 Hide don't touch my dhcp 2025-02-20 00:25:24 +03:00
18 changed files with 5465 additions and 3716 deletions

View File

@@ -11,6 +11,22 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.2.1 - uses: actions/checkout@v4.2.1
- name: Check version match
run: |
PODKOP_VERSION=$(grep '^PKG_VERSION:=' podkop/Makefile | cut -d '=' -f 2)
LUCI_APP_PODKOP_VERSION=$(grep '^PKG_VERSION:=' luci-app-podkop/Makefile | cut -d '=' -f 2)
TAG_VERSION=${GITHUB_REF#refs/tags/v}
echo "Podkop version: $PODKOP_VERSION"
echo "Luci-app-podkop version: $LUCI_APP_PODKOP_VERSION"
echo "Tag version: $TAG_VERSION"
if [ "$PODKOP_VERSION" != "$TAG_VERSION" ] || [ "$LUCI_APP_PODKOP_VERSION" != "$TAG_VERSION" ]; then
echo "Error: Version mismatch"
exit 1
fi
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v6.9.0
with: with:

279
README.md
View File

@@ -1,268 +1,59 @@
# Вещи, которые вам нужно знать перед установкой # Вещи, которые вам нужно знать перед установкой
- Это альфа версия, которая находится в активной разработке. Из версии в версию что-то может меняться. - Это бета-версия, которая находится в активной разработке. Из версии в версию что-то может меняться.
- Основной функционал работает, но побочные штуки сейчас могут сбоить. - При возникновении проблем, нужен технически грамотный фидбэк в чат.
- При обновлении **обязатально** сбрасывайте кэш LuCI. - При обновлении **обязательно** [сбрасывайте кэш LuCI](https://podkop.net/docs/clearbrowsercache/).
- Также при обновлении всегда заходите в конфигурацию и проверяйте свои настройки. Конфигурация может измениться. - Также при обновлении всегда заходите в конфигурацию и проверяйте свои настройки. Конфигурация может измениться.
- Необходимо минимум 15МБ свободного места на роутере. Роутерами с флешками на 16МБ сразу мимо. - Необходимо минимум 15МБ свободного места на роутере. Роутеры с флешками на 16МБ сразу мимо.
- При старте программы редактируется конфиг Dnsmasq. - При старте программы редактируется конфиг Dnsmasq.
- Podkop редактирует конфиг sing-box. Обязательно сохраните ваш конфиг sing-box перед установкой, если он вам нужен. - Podkop редактирует конфиг sing-box. Обязательно сохраните ваш конфиг sing-box перед установкой, если он вам нужен.
- Информация здесь может быть устаревшей. Все изменения фиксируются в телеграм-чате https://t.me/itdogchat - топик **Podkop**. - Информация здесь может быть устаревшей. Все изменения фиксируются в [телеграм-чате](https://t.me/itdogchat/81758/420321).
- Если у вас не что-то не работает, то следуюет сходить в телеграм чат, прочитать закрепы и выполнить что там написано.. - [Если у вас не что-то не работает.](https://podkop.net/docs/diagnostics/)
- Если у вас установлен Getdomains, его следует удалить. - Если у вас установлен Getdomains, [его следует удалить](https://github.com/itdoginfo/domain-routing-openwrt?tab=readme-ov-file#%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82-%D0%B4%D0%BB%D1%8F-%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F).
# Удаление GetDomains скриптом # Документация
``` https://podkop.net/
sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/domain-routing-openwrt/refs/heads/master/getdomains-uninstall.sh)
```
Оставляет туннели, зоны, forwarding. А также stubby и dnscrypt. Они не помешают. Конфиг sing-box будет перезаписан в podkop.
# Установка Podkop # Установка Podkop
Пакет работает на всех архитектурах. Полная информация в [документации](https://podkop.net/docs/install/)
Тестировался на **ванильной** OpenWrt 23.05 и OpenWrt 24.10.
На FriendlyWrt 23.05 присуствуют зависимости от iptables, которые ломают tproxy. Если у вас появляется warning про это в логах, следуйте инструкции по приведённой там ссылке.
Поддержки APK на данный момент нет. APK будет сделан после того как разгребу основное. Вкратце, достаточно одного скрипта для установки:
## Автоматическая
``` ```
sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh) sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh)
``` ```
Скрипт также предложит выбрать, какой туннель будет использоваться. Для выбранного туннеля будут установлены нужные пакеты, а для Wireguard и AmneziaWG также будет предложена автоматическая настройка - прямо в консоли скрипт запросит данные конфига. Для AmneziaWG можно также выбрать вариант с использованием конфига обычного Wireguard и автоматической обфускацией до AmneziaWG. Для обновления:
Для AmneziaWG скрипт проверяет наличие пакетов под вашу платформу в [стороннем репозитории](https://github.com/Slava-Shchipunov/awg-openwrt/releases), так как в официальном репозитории OpenWRT они отсутствуют, и автоматически их устанавливает.
## Вручную
Сделать `opkg update`, чтоб установились зависимости.
Скачать пакеты `podkop_*.ipk` и `luci-app-podkop_*.ipk` из релиза. `opkg install` сначала первый, потом второй.
# Обновление
Та же самая команда, что для установки. Скрипт обнаружит уже установленный podkop и предложит обновиться.
``` ```
sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh) sh <(wget -qO- https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh) --upgrade
``` ```
# Удаление
```
opkg remove luci-i18n-podkop-ru luci-app-podkop podkop
```
Если был установлен русский язык
```
opkg remove luci-i18n-podkop-ru
```
# Использование
Конфиг: /etc/config/podkop
Luci: Services/podkop
## Режимы
### Proxy
Для VLESS и Shadowsocks. Другие протоколы тоже будут, кидайте в чат примеры строк без чувствительных данных.
В этом режиме просто копируйте строку в **Proxy String** и из неё автоматически настроится sing-box.
### VPN
Здесь у вас должен быть уже настроен WG/OpenVPN/OpenConnect etc, зона Zone и Forwarding не обязательны.
Просто выбрать интерфейс из списка.
## Настройка доменов и подсетей
**Community Lists** - Включить списки комьюнити
**Subnets list enable** - Включить подсети из общего списка, выбрать из предложенных.
**Custom domains enable** - Добавить свои домены
**Custom subnets enable** - Добавить подсети или IP-адреса. Для подсетей задать маску.
# Известные баги
- [x] Не работает proxy при режимах main vpn, second proxy
- [x] Не всегда отрабатывает ucitrack (применение настроек из luci). Не удаётся повторить
- [x] All traffic for IP ломает инет на клиенте. Proxy mode
- [x] Не отрабатывает рестарт, при awg и не применяются изменения при awg
- [x] awg работает не стабильно
- [x] Сеть рестартится при любом раскладе
- [x] Выкл-вкл wg через luci не отрабатывает поднятие маршрута
- [ ] Если eof после последней строки в rt_tables, то скрипт не добавляет перенос строки
- [ ] Парсинг VLESS не отрабатывает, если в SNI два домена. Пример `sni=telegram.org%3Bwww.telegram.org`
- [ ] `service network restart` ломает маршруты при sing-box
- [ ] Совпадение секции с ruleset ломает конфиг sing-box
- [ ] В каких-то случаях плохо отрабатывает localfile
- [ ] exit 1 если в конфиге присуствует
```
option doh_backup_noresolv '0'
list doh_backup_server ''
list doh_backup_server ''
list doh_server '127.0.0.1#5053'
list doh_server '127.0.0.1#5054'
```
- [x] Только кастомный remote list не создаёт секцию в route-rules-rule-set и dns-rules-ruleset
- [ ] Не отрабатывает service podkop stop, если podkop запущен и не может, к пример, зарезолвить домен с сломанным DNS
- [ ] Всплывает в логах при старте. Не каждый раз. На работу не влияет. Wed Feb 19 17:12:28 2025 daemon.err sh[17665]: Command failed: ubus call service delete { "name": "sing-box" } (Not found)
# ToDo # ToDo
Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме. Этот раздел не означает задачи, которые нужно брать и делать. Это общий список хотелок. Если вы хотите помочь, пожалуйста, спросите сначала в телеграмме.
Сделано Основные задачи в issues.
- [x] Скрипт для автоматической установки.
- [x] Подсети дискорда.
- [x] Удаление getdomains через скрипт. Кроме туннеля и sing-box.
- [x] Дополнительная вкладка для ещё одного туннеля. Домены, подсети.
- [x] Улучшение скрипта автоматической установки. Спрашивать про туннели.
- [x] Зависимость от dnsmasq-full
- [x] Весь трафик для устойства пускать в туннель\прокси
- [x] Исключение для IP, не ходить в туннель\прокси совсем 0x0
- [x] Врубать галочкой yacd в sing-box
- [x] Свои списки. Просто список доменов с переносом строки
- [x] Свои списки ipv4
- [x] В nft разделить правило tproxy на маркировку и tproxy
- [x] Вернуть две цепочки nft
- [x] Ntp (порт 123) делать маркировку 0x0. По галке
- [x] Открытый прокси порт на роутере для браузеров
- [x] Автонастройка wireguard по примеру getdomains
- [x] Автонастройка awg по примеру getdomains
- [x] RU перевод
- [x] Переделать на PROCD и выкинуть ucitrack.
- [x] Нужен дебаг. Restart ucitrack в отдельный скрипт postinst, не отрабатывает.
- [x] Закомментировать дефолтные значения у list. interface поставить в пустое.
- [x] Скрипт установки: проверка установлен ли уже podkop. Если да, то просто предлагать обновится без установки тунелей и прокси.
Приоритет 1 ## Рефактор
- [x] Изменить название "Alternative Config" - [ ] Очевидные повторения в `/usr/bin/podkop` загнать в переменые
- [x] "domain_service_enabled" Добавить _second - [ ] Возможно поменять структуру
- [x] Установка Ru пакета в install.sh
- [x] Правка nft mark, tproxy
- [x] Правка перевода минимальная
- [x] Вставлять готовый outdbound вместо строки. Отдельная галка, которая в идеале должны скрывать поле для строки
- [ ] udp over tcp для ss сделать с выбором:
1) отключен (ПО на сервере -Shadowsocks)
2) включен, версия 2 (новые релизы xray-core, sing-box на сервере)
3) включен, версия 1 (старые релизы xray, sing-box на сервере)
Проблема в том, что это нужно только если SS. Выставлять выбор при парсинг из конфига вопрос можно ли. Если совсем тупо - сделать костыль в допонительные настройки
- [x] Проверка места в скрипте install. Если доступно меньше 20MB - exit 1 c выводом колько надо и сколько доступно. + показ модели роутера
- [ ] Правило запрещающее QUIC
- [ ] Проверить обновление списков, отрабатывает ли
- [ ] Проверка на ванильную openwrt
- [ ] Проверка откуда установлен sing-box. Например, проверять установлен ли он из официального репозитория
- [x] TG в сервисы
- [ ] Выбор ткуда направлять трафик в туннель. В том числе чтоб откуда угодно, а не только br-lan
- [ ] Диагностика: Proxy check completed successfully предположительно не показывает IP, если вернулся это IPv6.
- [ ] Диагностика: podkop_domains: 0 elements как проверять что доходят запросы при fakeip? Мб врубать логи dnsmasq и их чекать.
- [ ] Сделать галку запрещающую подкопу редачить dhcp. Допилить в исключение вместе с пустыми полями proxy и vpn
- [ ] Валидации предустановленных значений. Если прописаны другие, то вывод в лог о неизвестной переменной и продолжение работы
- [ ] Добавление в список доменов домены первого уровня (LuCI)
- [ ] Проверка, что версия в makefile совпадает с тегом
- [ ] Don't touch my DHCP!
Приоритет 2 ## Списки
- [x] Списки доменов и подсетей с роутера - [ ] Speedtest
- [ ] Кнопка обновления списка доменов и подсетей. Запихнуть в главное меню - [x] Google AI
- [ ] IPv6 - [x] Google PlayMarket. Здесь уточнить, что точно не работает через корректную настройку FakeIP, а не dnsmasq+nft.
- [x] Hetzner ASN (AS24940)
- [x] OVH ASN (AS16276)
Wiki ## Будущее
- [x] Тема - [ ] После наполнения вики про туннели, убрать всё что связано с их установкой из скрипта. Только с AWG что-то решить, лучше чтоб был скрипт в сторонем репозитории.
- [x] Изначальное наполнение - [ ] Подписка. Здесь нужна реализация, чтоб для каждой секции помимо ручного выбора, был выбор фильтрации по тегу. Например, для main выбираем ключевые слова NL, DE, FI. А для extra секции фильтруем по RU. И создаётся outbound c urltest в которых перечислены outbound из фильтров.
- [ ] Опция, когда все запросы (с роутера в первую очередь), а не только br-lan идут в прокси. С этим связана #95. Требуется много переделать для nftables.
- [ ] Весь трафик в Proxy\VPN. Вопрос, что делать с экстрасекциями в этом случае. FakeIP здесь скорее не нужен, а значит только main секция остаётся. Всё что касается fakeip проверок, придётся выключать в этом режиме.
- [ ] Поддержка Source format. Нужна расшифровка в json и если присуствуют подсети, заносить их в custom subnet nftset.
- [ ] Переделывание функции формирования кастомных списков в JSON. Обрабатывать сразу скопом, а не по одному.
- [ ] При успешном запуске переходит в фоновый режим и следит за состоянием sing-box. Если вдруг идёт exit 1, выполняется dnsmasq restore и снова следит за состоянием. Вопрос в том, как это искусcтвенно провернуть. Попробовать положить прокси и посмотреть, останется ли работать DNS в этом случае. И здесь, вероятно, можно обойтись триггером в init.d.
- [ ] Галочка, которая режет доступ к doh серверам.
- [ ] IPv6. Только после наполнения Wiki.
Низкий приоритет ## Тесты
- [x] Переменная, раз во сколько часов обновлять списки
- [ ] Галочка, которая режет доступ к doh серверам
- [ ] Свой конфиг sing-box
- [x] Поменять curl на wget, убрать зависимость. Проверять доступность списков лучше всего curl`ом
Рефактор
- [ ] Handle для sing-box
- [ ] Handle для dnsmasq
- [ ] Формирование json для sing-box на уровне jq, а не шаблонов
- [ ] Unit тесты (BATS) - [ ] Unit тесты (BATS)
- [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS) - [ ] Интеграционые тесты бекенда (OpenWrt rootfs + BATS)
Хз как сделать
- [ ] Добавить label от конфига vless\ss\etc в luci.
# Установка версии v0.2.5
Удаляет полностью все пакеты podkop. Удаляет текущую конфигурацию podkop.
После установки **обязательно** сбросьте кэш в LuCI.
```
sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install-v0.2.5.sh)
```
# Разработка
Есть два варианта:
- Просто поставить пакет на роутер или виртуалку и прям редактировать через SFTP (opkg install openssh-sftp-server)
- SDK, чтоб собирать пакеты
Для сборки пакетов нужен SDK, один из вариантов скачать прям файл и разархивировать
https://downloads.openwrt.org/releases/23.05.5/targets/x86/64/
Нужен файл с SDK в имени
```
wget https://downloads.openwrt.org/releases/23.05.5/targets/x86/64/openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64.tar.xz
tar xf openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64.tar.xz
mv openwrt-sdk-23.05.5-x86-64_gcc-12.3.0_musl.Linux-x86_64 SDK
```
Последнее для удобства.
Создаём директорию для пакета
```
mkdir package/utilites
```
Симлинк из репозитория
```
ln -s ~/podkop/podkop package/utilites/podkop
ln -s ~/podkop/luci-app-podkop package/luci-app-podkop
```
В первый раз для сборки luci-app необходимо обновить пакеты
```
./scripts/feeds update -a
```
Для make можно добавить флаг -j N, где N - количество ядер для сборки. Первый раз пройдёт быстрее.
При первом make выводится менюшка, можно просто save, exit и всё. Первый раз долго грузит зависимости.
Сборка пакета. Сами пакеты собираются быстро.
```
make package/podkop/{clean,compile} V=s
```
Также для luci
```
make package/luci-app-podkop/{clean,compile} V=s
```
.ipk лежат в `bin/packages/x86_64/base/`
## Примеры строкs
https://github.com/itdoginfo/podkop/blob/main/String-example.md
## Ошибки
```
Makefile:17: /SDK/feeds/luci/luci.mk: No such file or directory
make[2]: *** No rule to make target '/SDK/feeds/luci/luci.mk'. Stop.
time: package/luci/luci-app-podkop/clean#0.00#0.00#0.00
ERROR: package/luci/luci-app-podkop failed to build.
make[1]: *** [package/Makefile:129: package/luci/luci-app-podkop/clean] Error 1
make[1]: Leaving directory '/SDK'
make: *** [/SDK/include/toplevel.mk:226: package/luci-app-podkop/clean] Error 2
```
Не загружены пакеты для luci
## make зависимости
https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem
Ubuntu
```
sudo apt update
sudo apt install build-essential clang flex bison g++ gawk \
gcc-multilib g++-multilib gettext git libncurses-dev libssl-dev \
python3-distutils rsync unzip zlib1g-dev file wget
```

View File

@@ -1,97 +0,0 @@
#!/bin/sh
REPO="https://api.github.com/repos/itdoginfo/podkop/releases/tags/v0.2.5"
DOWNLOAD_DIR="/tmp/podkop"
COUNT=3
rm -rf "$DOWNLOAD_DIR"
mkdir -p "$DOWNLOAD_DIR"
main() {
check_system
opkg update
if [ -f "/etc/init.d/podkop" ]; then
echo "Remove current vesrion podkop"
opkg remove luci-i18n-podkop-ru luci-app-podkop podkop
rm /etc/config/podkop
else
echo "Installed podkop..."
fi
wget -qO- "$REPO" | grep -o 'https://[^"[:space:]]*\.ipk' | while read -r url; do
filename=$(basename "$url")
filepath="$DOWNLOAD_DIR/$filename"
attempt=0
while [ $attempt -lt $COUNT ]; do
if [ -f "$filepath" ] && [ -s "$filepath" ]; then
echo "$filename has already been uploaded"
break
fi
echo "Download $filename (count $((attempt+1)))..."
wget -q -O "$filepath" "$url"
if [ -s "$filepath" ]; then
echo "$filename successfully downloaded"
else
echo "Download error $filename. Retry..."
rm -f "$filepath"
fi
attempt=$((attempt+1))
done
done
for pkg in podkop luci-app-podkop; do
file=$(ls "$DOWNLOAD_DIR" | grep "^$pkg" | head -n 1)
if [ -n "$file" ]; then
echo "Installing $file"
opkg install "$DOWNLOAD_DIR/$file"
fi
done
ru=$(ls "$DOWNLOAD_DIR" | grep "luci-i18n-podkop-ru" | head -n 1)
if [ -n "$ru" ]; then
printf "\033[32;1mРусский язык интерфейса ставим? y/n (Need a Russian translation?)\033[0m "
while true; do
read -r -p '' RUS
case $RUS in
y)
opkg install "$DOWNLOAD_DIR/$ru"
break
;;
n)
break
;;
*)
echo "Введите y или n"
;;
esac
done
fi
rm -f $DOWNLOAD_DIR/podkop*.ipk $DOWNLOAD_DIR/luci-app-podkop*.ipk $DOWNLOAD_DIR/luci-i18n-podkop-ru*.ipk
}
check_system() {
# Get router model
MODEL=$(cat /tmp/sysinfo/model)
echo "Router model: $MODEL"
if ! nslookup google.com >/dev/null 2>&1; then
log "DNS not working"
exit 1
fi
if opkg list-installed | grep -qE "iptables|kmod-iptab"; then
printf "\033[31;1mFound incompatible iptables packages. If you're using FriendlyWrt: https://t.me/itdogchat/44512/181082\033[0m\n"
fi
}
main

View File

@@ -5,10 +5,17 @@ REPO="https://api.github.com/repos/itdoginfo/podkop/releases/latest"
IS_SHOULD_RESTART_NETWORK= IS_SHOULD_RESTART_NETWORK=
DOWNLOAD_DIR="/tmp/podkop" DOWNLOAD_DIR="/tmp/podkop"
COUNT=3 COUNT=3
UPGRADE=0
rm -rf "$DOWNLOAD_DIR" rm -rf "$DOWNLOAD_DIR"
mkdir -p "$DOWNLOAD_DIR" mkdir -p "$DOWNLOAD_DIR"
for arg in "$@"; do
if [ "$arg" = "--upgrade" ]; then
UPGRADE=1
fi
done
main() { main() {
check_system check_system
sing_box sing_box
@@ -16,32 +23,47 @@ main() {
opkg update opkg update
if [ -f "/etc/init.d/podkop" ]; then if [ -f "/etc/init.d/podkop" ]; then
printf "\033[32;1mPodkop is already installed. Just upgrade it? (y/n)\033[0m\n" if [ "$UPGRADE" -eq 1 ]; then
printf "\033[32;1my - Only upgrade podkop\033[0m\n" echo "Upgraded podkop with flag..."
printf "\033[32;1mn - Upgrade and install tunnels (WG, AWG, OpenVPN, OC)\033[0m\n" break
else
printf "\033[32;1mPodkop is already installed. Just upgrade it?\033[0m\n"
printf "\033[32;1my - Only upgrade podkop\033[0m\n"
printf "\033[32;1mn - Upgrade and install tunnels (WG, AWG, OpenVPN, OC)\033[0m\n"
while true; do while true; do
read -r -p '' UPDATE printf "\033[32;1mEnter (y/n): \033[0m"
case $UPDATE in read -r -p '' UPDATE
y) case $UPDATE in
echo "Upgraded podkop..." y)
break echo "Upgraded podkop..."
;; break
;;
n) n)
add_tunnel add_tunnel
break break
;; ;;
*) *)
echo "Please enter y or n" echo "Please enter y or n"
;; ;;
esac esac
done done
fi
else else
echo "Installed podkop..." echo "Installed podkop..."
add_tunnel add_tunnel
fi fi
if command -v curl &> /dev/null; then
check_response=$(curl -s "https://api.github.com/repos/itdoginfo/podkop/releases/latest")
if echo "$check_response" | grep -q 'API rate limit '; then
echo "You've reached rate limit from GitHub. Repeat in five minutes."
exit 1
fi
fi
download_success=0 download_success=0
while read -r url; do while read -r url; do
@@ -111,7 +133,7 @@ main() {
} }
add_tunnel() { add_tunnel() {
printf "\033[32;1mWill you be using Wireguard, AmneziaWG, OpenVPN, OpenConnect? If yes, select a number and they will be automatically installed\033[0m " printf "\033[32;1mWill you be using Wireguard, AmneziaWG, OpenVPN, OpenConnect? If yes, select a number and they will be automatically installed\033[0m\n"
echo "1) Wireguard" echo "1) Wireguard"
echo "2) AmneziaWG" echo "2) AmneziaWG"
echo "3) OpenVPN" echo "3) OpenVPN"
@@ -151,13 +173,13 @@ add_tunnel() {
;; ;;
3) 3)
opkg install opkg install openvpn-openssl luci-app-openvpn opkg install openvpn-openssl luci-app-openvpn
printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openvpn-na-openwrt/\e[0m\n" printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openvpn-na-openwrt/\e[0m\n"
break break
;; ;;
4) 4)
opkg install opkg install openconnect luci-proto-openconnect opkg install openconnect luci-proto-openconnect
printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openconnect-na-openwrt/\e[0m\n" printf "\e[1;32mUse these instructions to configure https://itdog.info/nastrojka-klienta-openconnect-na-openwrt/\e[0m\n"
break break
;; ;;
@@ -239,8 +261,8 @@ install_awg_packages() {
fi fi
fi fi
if opkg list-installed | grep -q luci-app-amneziawg; then if opkg list-installed | grep -qE 'luci-app-amneziawg|luci-proto-amneziawg'; then
echo "luci-app-amneziawg already installed" echo "luci-app-amneziawg or luci-proto-amneziawg already installed"
else else
LUCI_APP_AMNEZIAWG_FILENAME="luci-app-amneziawg${PKGPOSTFIX}" LUCI_APP_AMNEZIAWG_FILENAME="luci-app-amneziawg${PKGPOSTFIX}"
DOWNLOAD_URL="${BASE_URL}v${VERSION}/${LUCI_APP_AMNEZIAWG_FILENAME}" DOWNLOAD_URL="${BASE_URL}v${VERSION}/${LUCI_APP_AMNEZIAWG_FILENAME}"
@@ -412,11 +434,30 @@ check_system() {
fi fi
if ! nslookup google.com >/dev/null 2>&1; then if ! nslookup google.com >/dev/null 2>&1; then
log "DNS not working" printf "\033[31;1mDNS not working\033[0m\n"
exit 1 exit 1
fi fi
if opkg list-installed | grep -qE "iptables|kmod-iptab"; then if opkg list-installed | grep -q https-dns-proxy; then
printf "\033[31;1mСonflicting package detected: https-dns-proxy. Remove? yes/no\033[0m\n"
while true; do
read -r -p '' DNSPROXY
case $DNSPROXY in
yes|y|Y|yes)
opkg remove --force-depends luci-app-https-dns-proxy https-dns-proxy luci-i18n-https-dns-proxy*
break
;;
*)
echo "Exit"
exit 1
;;
esac
done
fi
if opkg list-installed | grep -q "iptables-mod-extra"; then
printf "\033[31;1mFound incompatible iptables packages. If you're using FriendlyWrt: https://t.me/itdogchat/44512/181082\033[0m\n" printf "\033[31;1mFound incompatible iptables packages. If you're using FriendlyWrt: https://t.me/itdogchat/44512/181082\033[0m\n"
fi fi
} }

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-podkop PKG_NAME:=luci-app-podkop
PKG_VERSION:=0.3.11 PKG_VERSION:=0.4.1
PKG_RELEASE:=1 PKG_RELEASE:=1
LUCI_TITLE:=LuCI podkop app LUCI_TITLE:=LuCI podkop app

View File

@@ -0,0 +1,195 @@
'use strict';
'require form';
'require baseclass';
'require view.podkop.constants as constants';
'require tools.widgets as widgets';
function createAdditionalSection(mainSection, network) {
let o = mainSection.tab('additional', _('Additional Settings'));
o = mainSection.taboption('additional', form.Flag, 'yacd', _('Yacd enable'), _('<a href="http://openwrt.lan:9090/ui" target="_blank">openwrt.lan:9090/ui</a>'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.Flag, 'exclude_ntp', _('Exclude NTP'), _('For issues with open connections sing-box'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.Flag, 'quic_disable', _('QUIC disable'), _('For issues with the video stream'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.ListValue, 'update_interval', _('List Update Frequency'), _('Select how often the lists will be updated'));
Object.entries(constants.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => {
o.value(key, _(label));
});
o.default = '1d';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.ListValue, 'dns_type', _('DNS Protocol Type'), _('Select DNS protocol to use'));
o.value('doh', _('DNS over HTTPS (DoH)'));
o.value('dot', _('DNS over TLS (DoT)'));
o.value('udp', _('UDP (Unprotected DNS)'));
o.default = 'doh';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.Value, 'dns_server', _('DNS Server'), _('Select or enter DNS server address'));
Object.entries(constants.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
o.value(key, _(label));
});
o.default = '8.8.8.8';
o.rmempty = false;
o.ucisection = 'main';
o.validate = function (section_id, value) {
if (!value) {
return _('DNS server address cannot be empty');
}
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipRegex.test(value)) {
const parts = value.split('.');
for (const part of parts) {
const num = parseInt(part);
if (num < 0 || num > 255) {
return _('IP address parts must be between 0 and 255');
}
}
return true;
}
const domainRegex = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$/;
if (!domainRegex.test(value)) {
return _('Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH');
}
return true;
};
o = mainSection.taboption('additional', form.Value, 'dns_rewrite_ttl', _('DNS Rewrite TTL'), _('Time in seconds for DNS record caching (default: 60)'));
o.default = '60';
o.rmempty = false;
o.ucisection = 'main';
o.validate = function (section_id, value) {
if (!value) {
return _('TTL value cannot be empty');
}
const ttl = parseInt(value);
if (isNaN(ttl) || ttl < 0) {
return _('TTL must be a positive number');
}
return true;
};
o = mainSection.taboption('additional', form.Value, 'cache_file', _('Cache File Path'), _('Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing'));
o.value('/tmp/cache.db', 'RAM (/tmp/cache.db)');
o.value('/usr/share/sing-box/cache.db', 'Flash (/usr/share/sing-box/cache.db)');
o.default = '/tmp/cache.db';
o.rmempty = false;
o.ucisection = 'main';
o.validate = function (section_id, value) {
if (!value) {
return _('Cache file path cannot be empty');
}
if (!value.startsWith('/')) {
return _('Path must be absolute (start with /)');
}
if (!value.endsWith('cache.db')) {
return _('Path must end with cache.db');
}
const parts = value.split('/').filter(Boolean);
if (parts.length < 2) {
return _('Path must contain at least one directory (like /tmp/cache.db)');
}
return true;
};
o = mainSection.taboption('additional', widgets.DeviceSelect, 'iface', _('Source Network Interface'), _('Select the network interface from which the traffic will originate'));
o.ucisection = 'main';
o.default = 'br-lan';
o.noaliases = true;
o.nobridges = false;
o.noinactive = false;
o.multiple = true;
o.filter = function (section_id, value) {
if (['wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan'].indexOf(value) !== -1) {
return false;
}
var device = this.devices.filter(function (dev) {
return dev.getName() === value;
})[0];
if (device) {
var type = device.getType();
return type !== 'wifi' && type !== 'wireless' && !type.includes('wlan');
}
return true;
};
o = mainSection.taboption('additional', form.Flag, 'mon_restart_ifaces', _('Interface monitoring'), _('Interface monitoring for bad WAN'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', widgets.NetworkSelect, 'restart_ifaces', _('Interface for monitoring'), _('Select the WAN interfaces to be monitored'));
o.ucisection = 'main';
o.depends('mon_restart_ifaces', '1');
o.multiple = true;
o.filter = function (section_id, value) {
return ['lan', 'loopback'].indexOf(value) === -1 && !value.startsWith('@');
};
o = mainSection.taboption('additional', form.Flag, 'dont_touch_dhcp', _('Dont touch my DHCP!'), _('Podkop will not change the DHCP config'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('additional', form.Flag, 'detour', _('Proxy download of lists'), _('Downloading all lists via main Proxy/VPN'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
// Extra IPs and exclusions (main section)
o = mainSection.taboption('basic', form.Flag, 'exclude_from_ip_enabled', _('IP for exclusion'), _('Specify local IP addresses that will never use the configured route'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
o = mainSection.taboption('basic', form.DynamicList, 'exclude_traffic_ip', _('Local IPs'), _('Enter valid IPv4 addresses'));
o.placeholder = 'IP';
o.depends('exclude_from_ip_enabled', '1');
o.rmempty = false;
o.ucisection = 'main';
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipRegex.test(value)) return _('Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)');
const ipParts = value.split('.');
for (const part of ipParts) {
const num = parseInt(part);
if (num < 0 || num > 255) return _('IP address parts must be between 0 and 255');
}
return true;
};
o = mainSection.taboption('basic', form.Flag, 'socks5', _('Mixed enable'), _('Browser port: 2080'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
}
return baseclass.extend({
createAdditionalSection
});

View File

@@ -0,0 +1,533 @@
'use strict';
'require baseclass';
'require form';
'require ui';
'require network';
'require view.podkop.constants as constants';
'require tools.widgets as widgets';
function validateUrl(url, protocols = ['http:', 'https:']) {
try {
const parsedUrl = new URL(url);
if (!protocols.includes(parsedUrl.protocol)) {
return _('URL must use one of the following protocols: ') + protocols.join(', ');
}
return true;
} catch (e) {
return _('Invalid URL format');
}
}
function createConfigSection(section, map, network) {
const s = section;
let o = s.tab('basic', _('Basic Settings'));
o = s.taboption('basic', form.ListValue, 'mode', _('Connection Type'), _('Select between VPN and Proxy connection methods for traffic routing'));
o.value('proxy', ('Proxy'));
o.value('vpn', ('VPN'));
o.value('block', ('Block'));
o.ucisection = s.section;
o = s.taboption('basic', form.ListValue, 'proxy_config_type', _('Configuration Type'), _('Select how to configure the proxy'));
o.value('url', _('Connection URL'));
o.value('outbound', _('Outbound Config'));
o.default = 'url';
o.depends('mode', 'proxy');
o.ucisection = s.section;
o = s.taboption('basic', form.TextValue, 'proxy_string', _('Proxy Configuration URL'), _(''));
o.depends('proxy_config_type', 'url');
o.rows = 5;
o.rmempty = false;
o.ucisection = s.section;
o.sectionDescriptions = new Map();
o.placeholder = 'vless://uuid@server:port?type=tcp&security=tls#main\n// backup ss://method:pass@server:port\n// backup2 vless://uuid@server:port?type=grpc&security=reality#alt';
o.renderWidget = function (section_id, option_index, cfgvalue) {
const original = form.TextValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]);
const container = E('div', {});
container.appendChild(original);
if (cfgvalue) {
try {
const activeConfig = cfgvalue.split('\n')
.map(line => line.trim())
.find(line => line && !line.startsWith('//'));
if (activeConfig) {
if (activeConfig.includes('#')) {
const label = activeConfig.split('#').pop();
if (label && label.trim()) {
const decodedLabel = decodeURIComponent(label);
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Current config: ') + decodedLabel);
container.appendChild(descDiv);
} else {
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
} else {
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
}
} catch (e) {
console.error('Error parsing config label:', e);
const descDiv = E('div', { 'class': 'cbi-value-description' }, _('Config without description'));
container.appendChild(descDiv);
}
} else {
const defaultDesc = E('div', { 'class': 'cbi-value-description' },
_('Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs'));
container.appendChild(defaultDesc);
}
return container;
};
o.validate = function (section_id, value) {
if (!value || value.length === 0) {
return true;
}
try {
const activeConfig = value.split('\n')
.map(line => line.trim())
.find(line => line && !line.startsWith('//'));
if (!activeConfig) {
return _('No active configuration found. At least one non-commented line is required.');
}
if (!activeConfig.startsWith('vless://') && !activeConfig.startsWith('ss://')) {
return _('URL must start with vless:// or ss://');
}
if (activeConfig.startsWith('ss://')) {
let encrypted_part;
try {
let mainPart = activeConfig.includes('?') ? activeConfig.split('?')[0] : activeConfig.split('#')[0];
encrypted_part = mainPart.split('/')[2].split('@')[0];
try {
let decoded = atob(encrypted_part);
if (!decoded.includes(':')) {
if (!encrypted_part.includes(':') && !encrypted_part.includes('-')) {
return _('Invalid Shadowsocks URL format: missing method and password separator ":"');
}
}
} catch (e) {
if (!encrypted_part.includes(':') && !encrypted_part.includes('-')) {
return _('Invalid Shadowsocks URL format: missing method and password separator ":"');
}
}
} catch (e) {
return _('Invalid Shadowsocks URL format');
}
try {
let serverPart = activeConfig.split('@')[1];
if (!serverPart) return _('Invalid Shadowsocks URL: missing server address');
let [server, portAndRest] = serverPart.split(':');
if (!server) return _('Invalid Shadowsocks URL: missing server');
let port = portAndRest ? portAndRest.split(/[?#]/)[0] : null;
if (!port) return _('Invalid Shadowsocks URL: missing port');
let portNum = parseInt(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
return _('Invalid port number. Must be between 1 and 65535');
}
} catch (e) {
return _('Invalid Shadowsocks URL: missing or invalid server/port format');
}
}
if (activeConfig.startsWith('vless://')) {
let uuid = activeConfig.split('/')[2].split('@')[0];
if (!uuid || uuid.length === 0) return _('Invalid VLESS URL: missing UUID');
try {
let serverPart = activeConfig.split('@')[1];
if (!serverPart) return _('Invalid VLESS URL: missing server address');
let [server, portAndRest] = serverPart.split(':');
if (!server) return _('Invalid VLESS URL: missing server');
let port = portAndRest ? portAndRest.split(/[/?#]/)[0] : null;
if (!port) return _('Invalid VLESS URL: missing port');
let portNum = parseInt(port);
if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
return _('Invalid port number. Must be between 1 and 65535');
}
} catch (e) {
return _('Invalid VLESS URL: missing or invalid server/port format');
}
let queryString = activeConfig.split('?')[1];
if (!queryString) return _('Invalid VLESS URL: missing query parameters');
let params = new URLSearchParams(queryString.split('#')[0]);
let type = params.get('type');
const validTypes = ['tcp', 'raw', 'udp', 'grpc', 'http', 'ws'];
if (!type || !validTypes.includes(type)) {
return _('Invalid VLESS URL: type must be one of tcp, raw, udp, grpc, http, ws');
}
let security = params.get('security');
const validSecurities = ['tls', 'reality', 'none'];
if (!security || !validSecurities.includes(security)) {
return _('Invalid VLESS URL: security must be one of tls, reality, none');
}
if (security === 'reality') {
if (!params.get('pbk')) return _('Invalid VLESS URL: missing pbk parameter for reality security');
if (!params.get('fp')) return _('Invalid VLESS URL: missing fp parameter for reality security');
}
if (security === 'tls' && type !== 'tcp' && !params.get('sni')) {
return _('Invalid VLESS URL: missing sni parameter for tls security');
}
}
return true;
} catch (e) {
console.error('Validation error:', e);
return _('Invalid URL format: ') + e.message;
}
};
o = s.taboption('basic', form.TextValue, 'outbound_json', _('Outbound Configuration'), _('Enter complete outbound configuration in JSON format'));
o.depends('proxy_config_type', 'outbound');
o.rows = 10;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
try {
const parsed = JSON.parse(value);
if (!parsed.type || !parsed.server || !parsed.server_port) {
return _('JSON must contain at least type, server and server_port fields');
}
return true;
} catch (e) {
return _('Invalid JSON format');
}
};
o = s.taboption('basic', form.Flag, 'ss_uot', _('Shadowsocks UDP over TCP'), _('Apply for SS2022'));
o.default = '0';
o.depends('mode', 'proxy');
o.rmempty = false;
o.ucisection = 'main';
o = s.taboption('basic', widgets.DeviceSelect, 'interface', _('Network Interface'), _('Select network interface for VPN connection'));
o.depends('mode', 'vpn');
o.ucisection = s.section;
o.noaliases = true;
o.nobridges = false;
o.noinactive = false;
o.filter = function (section_id, value) {
if (['br-lan', 'eth0', 'eth1', 'wan', 'phy0-ap0', 'phy1-ap0', 'pppoe-wan', 'lan'].indexOf(value) !== -1) {
return false;
}
var device = this.devices.filter(function (dev) {
return dev.getName() === value;
})[0];
if (device) {
var type = device.getType();
return type !== 'wifi' && type !== 'wireless' && !type.includes('wlan');
}
return true;
};
o = s.taboption('basic', form.Flag, 'domain_list_enabled', _('Community Lists'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'domain_list', _('Service List'), _('Select predefined service for routing') + ' <a href="https://github.com/itdoginfo/allow-domains" target="_blank">github.com/itdoginfo/allow-domains</a>');
o.placeholder = 'Service list';
Object.entries(constants.DOMAIN_LIST_OPTIONS).forEach(([key, label]) => {
o.value(key, _(label));
});
o.depends('domain_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
let lastValues = [];
let isProcessing = false;
o.onchange = function (ev, section_id, value) {
if (isProcessing) return;
isProcessing = true;
try {
const values = Array.isArray(value) ? value : [value];
let newValues = [...values];
let notifications = [];
const selectedRegionalOptions = constants.REGIONAL_OPTIONS.filter(opt => newValues.includes(opt));
if (selectedRegionalOptions.length > 1) {
const lastSelected = selectedRegionalOptions[selectedRegionalOptions.length - 1];
const removedRegions = selectedRegionalOptions.slice(0, -1);
newValues = newValues.filter(v => v === lastSelected || !constants.REGIONAL_OPTIONS.includes(v));
notifications.push(E('p', { class: 'alert-message warning' }, [
E('strong', {}, _('Regional options cannot be used together')), E('br'),
_('Warning: %s cannot be used together with %s. Previous selections have been removed.')
.format(removedRegions.join(', '), lastSelected)
]));
}
if (newValues.includes('russia_inside')) {
const removedServices = newValues.filter(v => !constants.ALLOWED_WITH_RUSSIA_INSIDE.includes(v));
if (removedServices.length > 0) {
newValues = newValues.filter(v => constants.ALLOWED_WITH_RUSSIA_INSIDE.includes(v));
notifications.push(E('p', { class: 'alert-message warning' }, [
E('strong', {}, _('Russia inside restrictions')), E('br'),
_('Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.')
.format(
constants.ALLOWED_WITH_RUSSIA_INSIDE.map(key => constants.DOMAIN_LIST_OPTIONS[key]).filter(label => label !== 'Russia inside').join(', '),
removedServices.join(', ')
)
]));
}
}
if (JSON.stringify(newValues.sort()) !== JSON.stringify(values.sort())) {
this.getUIElement(section_id).setValue(newValues);
}
notifications.forEach(notification => ui.addNotification(null, notification));
lastValues = newValues;
} catch (e) {
console.error('Error in onchange handler:', e);
} finally {
isProcessing = false;
}
};
o = s.taboption('basic', form.ListValue, 'custom_domains_list_type', _('User Domain List Type'), _('Select how to add your custom domains'));
o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List'));
o.default = 'disabled';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
o.placeholder = 'Domains list';
o.depends('custom_domains_list_type', 'dynamic');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*(\.[A-Za-z]{2,})?$/;
if (!domainRegex.test(value)) {
return _('Invalid domain format. Enter domain without protocol (example: sub.example.com or ru)');
}
return true;
};
o = s.taboption('basic', form.TextValue, 'custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline. You can add comments after //'));
o.placeholder = 'example.com, sub.example.com\n// Social networks\ndomain.com test.com // personal domains';
o.depends('custom_domains_list_type', 'text');
o.rows = 8;
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*(\.[A-Za-z]{2,})?$/;
const lines = value.split(/\n/).map(line => line.trim());
let hasValidDomain = false;
for (const line of lines) {
// Skip empty lines
if (!line) continue;
// Extract domain part (before any //)
const domainPart = line.split('//')[0].trim();
// Skip if line is empty after removing comments
if (!domainPart) continue;
// Process each domain in the line (separated by comma or space)
const domains = domainPart.split(/[,\s]+/).map(d => d.trim()).filter(d => d.length > 0);
for (const domain of domains) {
if (!domainRegex.test(domain)) {
return _('Invalid domain format: %s. Enter domain without protocol').format(domain);
}
hasValidDomain = true;
}
}
if (!hasValidDomain) {
return _('At least one valid domain must be specified. Comments-only content is not allowed.');
}
return true;
};
o = s.taboption('basic', form.Flag, 'custom_local_domains_list_enabled', _('Local Domain Lists'), _('Use the list from the router filesystem'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_local_domains', _('Local Domain Lists Path'), _('Enter the list file path'));
o.placeholder = '/path/file.lst';
o.depends('custom_local_domains_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const pathRegex = /^\/[a-zA-Z0-9_\-\/\.]+$/;
if (!pathRegex.test(value)) {
return _('Invalid path format. Path must start with "/" and contain valid characters');
}
return true;
};
o = s.taboption('basic', form.Flag, 'custom_download_domains_list_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_download_domains', _('Remote Domain URLs'), _('Enter full URLs starting with http:// or https://'));
o.placeholder = 'URL';
o.depends('custom_download_domains_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
return validateUrl(value);
};
o = s.taboption('basic', form.ListValue, 'custom_subnets_list_enabled', _('User Subnet List Type'), _('Select how to add your custom subnets'));
o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List (comma/space/newline separated)'));
o.default = 'disabled';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_subnets', _('User Subnets'), _('Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses'));
o.placeholder = 'IP or subnet';
o.depends('custom_subnets_list_enabled', 'dynamic');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
if (!subnetRegex.test(value)) return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y');
const [ip, cidr] = value.split('/');
const ipParts = ip.split('.');
for (const part of ipParts) {
const num = parseInt(part);
if (num < 0 || num > 255) return _('IP address parts must be between 0 and 255');
}
if (cidr !== undefined) {
const cidrNum = parseInt(cidr);
if (cidrNum < 0 || cidrNum > 32) return _('CIDR must be between 0 and 32');
}
return true;
};
o = s.taboption('basic', form.TextValue, 'custom_subnets_text', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //'));
o.placeholder = '103.21.244.0/22\n// Google DNS\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 // Cloudflare and Quad9';
o.depends('custom_subnets_list_enabled', 'text');
o.rows = 10;
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
const lines = value.split(/\n/).map(line => line.trim());
let hasValidSubnet = false;
for (const line of lines) {
// Skip empty lines
if (!line) continue;
// Extract subnet part (before any //)
const subnetPart = line.split('//')[0].trim();
// Skip if line is empty after removing comments
if (!subnetPart) continue;
// Process each subnet in the line (separated by comma or space)
const subnets = subnetPart.split(/[,\s]+/).map(s => s.trim()).filter(s => s.length > 0);
for (const subnet of subnets) {
if (!subnetRegex.test(subnet)) {
return _('Invalid format: %s. Use format: X.X.X.X or X.X.X.X/Y').format(subnet);
}
const [ip, cidr] = subnet.split('/');
const ipParts = ip.split('.');
for (const part of ipParts) {
const num = parseInt(part);
if (num < 0 || num > 255) {
return _('IP parts must be between 0 and 255 in: %s').format(subnet);
}
}
if (cidr !== undefined) {
const cidrNum = parseInt(cidr);
if (cidrNum < 0 || cidrNum > 32) {
return _('CIDR must be between 0 and 32 in: %s').format(subnet);
}
}
hasValidSubnet = true;
}
}
if (!hasValidSubnet) {
return _('At least one valid subnet or IP must be specified. Comments-only content is not allowed.');
}
return true;
};
o = s.taboption('basic', form.Flag, 'custom_download_subnets_list_enabled', _('Remote Subnet Lists'), _('Download and use subnet lists from remote URLs'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'custom_download_subnets', _('Remote Subnet URLs'), _('Enter full URLs starting with http:// or https://'));
o.placeholder = 'URL';
o.depends('custom_download_subnets_list_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
return validateUrl(value);
};
o = s.taboption('basic', form.Flag, 'all_traffic_from_ip_enabled', _('IP for full redirection'), _('Specify local IP addresses whose traffic will always use the configured route'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'all_traffic_ip', _('Local IPs'), _('Enter valid IPv4 addresses'));
o.placeholder = 'IP';
o.depends('all_traffic_from_ip_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (!ipRegex.test(value)) return _('Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)');
const ipParts = value.split('.');
for (const part of ipParts) {
const num = parseInt(part);
if (num < 0 || num > 255) return _('IP address parts must be between 0 and 255');
}
return true;
};
}
return baseclass.extend({
createConfigSection
});

View File

@@ -0,0 +1,107 @@
'use strict';
'require baseclass';
const STATUS_COLORS = {
SUCCESS: '#4caf50',
ERROR: '#f44336',
WARNING: '#ff9800'
};
const FAKEIP_CHECK_DOMAIN = 'fakeip.podkop.fyi';
const IP_CHECK_DOMAIN = 'ip.podkop.fyi';
const REGIONAL_OPTIONS = ['russia_inside', 'russia_outside', 'ukraine_inside'];
const ALLOWED_WITH_RUSSIA_INSIDE = [
'russia_inside',
'meta',
'twitter',
'discord',
'telegram',
'cloudflare',
'google_ai',
'google_play',
'hetzner',
'ovh'
];
const DOMAIN_LIST_OPTIONS = {
russia_inside: 'Russia inside',
russia_outside: 'Russia outside',
ukraine_inside: 'Ukraine',
geoblock: 'Geo Block',
block: 'Block',
porn: 'Porn',
news: 'News',
anime: 'Anime',
youtube: 'Youtube',
discord: 'Discord',
meta: 'Meta',
twitter: 'Twitter (X)',
hdrezka: 'HDRezka',
tiktok: 'Tik-Tok',
telegram: 'Telegram',
cloudflare: 'Cloudflare',
google_ai: 'Google AI',
google_play: 'Google Play',
hetzner: 'Hetzner ASN',
ovh: 'OVH ASN'
};
const UPDATE_INTERVAL_OPTIONS = {
'1h': 'Every hour',
'3h': 'Every 3 hours',
'12h': 'Every 12 hours',
'1d': 'Every day',
'3d': 'Every 3 days'
};
const DNS_SERVER_OPTIONS = {
'1.1.1.1': 'Cloudflare (1.1.1.1)',
'8.8.8.8': 'Google (8.8.8.8)',
'9.9.9.9': 'Quad9 (9.9.9.9)',
'dns.adguard-dns.com': 'AdGuard Default (dns.adguard-dns.com)',
'unfiltered.adguard-dns.com': 'AdGuard Unfiltered (unfiltered.adguard-dns.com)',
'family.adguard-dns.com': 'AdGuard Family (family.adguard-dns.com)'
};
const DIAGNOSTICS_UPDATE_INTERVAL = 10000; // 10 seconds
const CACHE_TIMEOUT = DIAGNOSTICS_UPDATE_INTERVAL - 1000; // 9 seconds
const ERROR_POLL_INTERVAL = 10000; // 10 seconds
const COMMAND_TIMEOUT = 10000; // 10 seconds
const FETCH_TIMEOUT = 10000; // 10 seconds
const BUTTON_FEEDBACK_TIMEOUT = 1000; // 1 second
const DIAGNOSTICS_INITIAL_DELAY = 100; // 100 milliseconds
// Массив задержек для приоритетов выполнения команд в диагностике (в миллисекундах)
const RUN_PRIORITY = [
0, // Приоритет 0 - Критический (выполняется немедленно)
100, // Приоритет 1 - Очень высокий
300, // Приоритет 2 - Высокий
500, // Приоритет 3 - Выше среднего
700, // Приоритет 4 - Средний
900, // Приоритет 5 - Ниже среднего
1100, // Приоритет 6 - Низкий
1300, // Приоритет 7 - Очень низкий
1500, // Приоритет 8 - Фоновый
1700, // Приоритет 9 - Отложенный
1900 // Приоритет 10 - Наименее важный
];
return baseclass.extend({
STATUS_COLORS,
FAKEIP_CHECK_DOMAIN,
IP_CHECK_DOMAIN,
REGIONAL_OPTIONS,
ALLOWED_WITH_RUSSIA_INSIDE,
DOMAIN_LIST_OPTIONS,
UPDATE_INTERVAL_OPTIONS,
DNS_SERVER_OPTIONS,
DIAGNOSTICS_UPDATE_INTERVAL,
ERROR_POLL_INTERVAL,
COMMAND_TIMEOUT,
FETCH_TIMEOUT,
BUTTON_FEEDBACK_TIMEOUT,
DIAGNOSTICS_INITIAL_DELAY,
RUN_PRIORITY,
CACHE_TIMEOUT
});

View File

@@ -0,0 +1,884 @@
'use strict';
'require baseclass';
'require form';
'require ui';
'require uci';
'require fs';
'require view.podkop.constants as constants';
'require view.podkop.utils as utils';
// Cache system for network requests
const fetchCache = {};
// Helper function to fetch with cache
async function cachedFetch(url, options = {}) {
const cacheKey = url;
const currentTime = Date.now();
// If we have a valid cached response, return it
if (fetchCache[cacheKey] && currentTime - fetchCache[cacheKey].timestamp < constants.CACHE_TIMEOUT) {
console.log(`Using cached response for ${url}`);
return Promise.resolve(fetchCache[cacheKey].response.clone());
}
// Otherwise, make a new request
try {
const response = await fetch(url, options);
// Cache the response
fetchCache[cacheKey] = {
response: response.clone(),
timestamp: currentTime
};
return response;
} catch (error) {
throw error;
}
}
// Helper functions for command execution with prioritization - Using from utils.js now
function safeExec(command, args, priority, callback, timeout = constants.COMMAND_TIMEOUT) {
return utils.safeExec(command, args, priority, callback, timeout);
}
// Helper functions for handling checks
function runCheck(checkFunction, priority, callback) {
priority = (typeof priority === 'number') ? priority : 0;
const executeCheck = async () => {
try {
const result = await checkFunction();
if (callback && typeof callback === 'function') {
callback(result);
}
return result;
} catch (error) {
if (callback && typeof callback === 'function') {
callback({ error });
}
return { error };
}
};
if (callback && typeof callback === 'function') {
setTimeout(executeCheck, constants.RUN_PRIORITY[priority]);
return;
} else {
return executeCheck();
}
}
function runAsyncTask(taskFunction, priority) {
priority = (typeof priority === 'number') ? priority : 0;
setTimeout(async () => {
try {
await taskFunction();
} catch (error) {
console.error('Async task error:', error);
}
}, constants.RUN_PRIORITY[priority]);
}
// Helper Functions for UI and formatting
function createStatus(state, message, color) {
return {
state,
message: _(message),
color: constants.STATUS_COLORS[color]
};
}
function formatDiagnosticOutput(output) {
if (typeof output !== 'string') return '';
return output.trim()
.replace(/\x1b\[[0-9;]*m/g, '')
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n');
}
function copyToClipboard(text, button) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
const originalText = button.textContent;
button.textContent = _('Copied!');
setTimeout(() => button.textContent = originalText, constants.BUTTON_FEEDBACK_TIMEOUT);
} catch (err) {
ui.addNotification(null, E('p', {}, _('Failed to copy: ') + err.message));
}
document.body.removeChild(textarea);
}
// IP masking function
function maskIP(ip) {
if (!ip) return '';
const parts = ip.split('.');
if (parts.length !== 4) return ip;
return ['XX', 'XX', 'XX', parts[3]].join('.');
}
// Status Check Functions
async function checkFakeIP() {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), constants.FETCH_TIMEOUT);
try {
const response = await cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal });
const data = await response.json();
clearTimeout(timeoutId);
if (data.fakeip === true) {
return createStatus('working', 'working', 'SUCCESS');
} else {
return createStatus('not_working', 'not working', 'ERROR');
}
} catch (fetchError) {
clearTimeout(timeoutId);
const message = fetchError.name === 'AbortError' ? 'timeout' : 'check error';
return createStatus('error', message, 'WARNING');
}
} catch (error) {
return createStatus('error', 'check error', 'WARNING');
}
}
async function checkFakeIPCLI() {
try {
return new Promise((resolve) => {
safeExec('/usr/bin/podkop', ['get_sing_box_status'], 0, singboxStatusResult => {
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
if (!singboxStatus.running) {
resolve(createStatus('not_working', 'sing-box not running', 'ERROR'));
return;
}
safeExec('nslookup', ['-timeout=2', constants.FAKEIP_CHECK_DOMAIN, '127.0.0.42'], 0, result => {
if (result.stdout && result.stdout.includes('198.18')) {
resolve(createStatus('working', 'working on router', 'SUCCESS'));
} else {
resolve(createStatus('not_working', 'not working on router', 'ERROR'));
}
});
});
});
} catch (error) {
return createStatus('error', 'CLI check error', 'WARNING');
}
}
function checkDNSAvailability() {
return new Promise(async (resolve) => {
try {
safeExec('/usr/bin/podkop', ['check_dns_available'], 0, dnsStatusResult => {
if (!dnsStatusResult || !dnsStatusResult.stdout) {
return resolve({
remote: createStatus('error', 'DNS check timeout', 'WARNING'),
local: createStatus('error', 'DNS check timeout', 'WARNING')
});
}
try {
const dnsStatus = JSON.parse(dnsStatusResult.stdout);
const remoteStatus = dnsStatus.is_available ?
createStatus('available', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) available`, 'SUCCESS') :
createStatus('unavailable', `${dnsStatus.dns_type.toUpperCase()} (${dnsStatus.dns_server}) unavailable`, 'ERROR');
const localStatus = dnsStatus.local_dns_working ?
createStatus('available', 'Router DNS working', 'SUCCESS') :
createStatus('unavailable', 'Router DNS not working', 'ERROR');
return resolve({
remote: remoteStatus,
local: localStatus
});
} catch (parseError) {
return resolve({
remote: createStatus('error', 'DNS check parse error', 'WARNING'),
local: createStatus('error', 'DNS check parse error', 'WARNING')
});
}
});
} catch (error) {
return resolve({
remote: createStatus('error', 'DNS check error', 'WARNING'),
local: createStatus('error', 'DNS check error', 'WARNING')
});
}
});
}
async function checkBypass() {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), constants.FETCH_TIMEOUT);
try {
const response1 = await cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal });
const data1 = await response1.json();
const response2 = await cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller.signal });
const data2 = await response2.json();
clearTimeout(timeoutId);
if (data1.IP && data2.IP) {
if (data1.IP !== data2.IP) {
return createStatus('working', 'working', 'SUCCESS');
} else {
return createStatus('not_working', 'same IP for both domains', 'ERROR');
}
} else {
return createStatus('error', 'check error (no IP)', 'WARNING');
}
} catch (fetchError) {
clearTimeout(timeoutId);
const message = fetchError.name === 'AbortError' ? 'timeout' : 'check error';
return createStatus('error', message, 'WARNING');
}
} catch (error) {
return createStatus('error', 'check error', 'WARNING');
}
}
// Modal Functions
function createModalContent(title, content) {
return [
E('div', {
'class': 'panel-body',
style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; ' +
'font-family: monospace; white-space: pre-wrap; word-wrap: break-word; ' +
'line-height: 1.5; font-size: 14px;'
}, [
E('pre', { style: 'margin: 0;' }, content)
]),
E('div', {
'class': 'right',
style: 'margin-top: 1em;'
}, [
E('button', {
'class': 'btn',
'click': ev => copyToClipboard('```txt\n' + content + '\n```', ev.target)
}, _('Copy to Clipboard')),
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Close'))
])
];
}
function showConfigModal(command, title) {
// Create and show modal immediately with loading state
const modalContent = E('div', { 'class': 'panel-body' }, [
E('div', {
'class': 'panel-body',
style: 'max-height: 70vh; overflow-y: auto; margin: 1em 0; padding: 1.5em; ' +
'font-family: monospace; white-space: pre-wrap; word-wrap: break-word; ' +
'line-height: 1.5; font-size: 14px;'
}, [
E('pre', {
'id': 'modal-content-pre',
style: 'margin: 0;'
}, _('Loading...'))
]),
E('div', {
'class': 'right',
style: 'margin-top: 1em;'
}, [
E('button', {
'class': 'btn',
'id': 'copy-button',
'click': ev => copyToClipboard('```txt\n' + document.getElementById('modal-content-pre').innerText + '\n```', ev.target)
}, _('Copy to Clipboard')),
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Close'))
])
]);
ui.showModal(_(title), modalContent);
// Function to update modal content
const updateModalContent = (content) => {
const pre = document.getElementById('modal-content-pre');
if (pre) {
pre.textContent = content;
}
};
try {
let formattedOutput = '';
if (command === 'global_check') {
safeExec('/usr/bin/podkop', [command], 0, res => {
formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), constants.FETCH_TIMEOUT);
cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal })
.then(response => response.json())
.then(data => {
clearTimeout(timeoutId);
if (data.fakeip === true) {
formattedOutput += '\n✅ ' + _('FakeIP is working in browser!') + '\n';
} else {
formattedOutput += '\n❌ ' + _('FakeIP is not working in browser') + '\n';
formattedOutput += _('Check DNS server on current device (PC, phone)') + '\n';
formattedOutput += _('Its must be router!') + '\n';
}
// Bypass check
cachedFetch(`https://${constants.FAKEIP_CHECK_DOMAIN}/check`, { signal: controller.signal })
.then(bypassResponse => bypassResponse.json())
.then(bypassData => {
cachedFetch(`https://${constants.IP_CHECK_DOMAIN}/check`, { signal: controller.signal })
.then(bypassResponse2 => bypassResponse2.json())
.then(bypassData2 => {
formattedOutput += '━━━━━━━━━━━━━━━━━━━━━━━━━━━\n';
if (bypassData.IP && bypassData2.IP && bypassData.IP !== bypassData2.IP) {
formattedOutput += '✅ ' + _('Proxy working correctly') + '\n';
formattedOutput += _('Direct IP: ') + maskIP(bypassData.IP) + '\n';
formattedOutput += _('Proxy IP: ') + maskIP(bypassData2.IP) + '\n';
} else if (bypassData.IP === bypassData2.IP) {
formattedOutput += '❌ ' + _('Proxy is not working - same IP for both domains') + '\n';
formattedOutput += _('IP: ') + maskIP(bypassData.IP) + '\n';
} else {
formattedOutput += '❌ ' + _('Proxy check failed') + '\n';
}
updateModalContent(formattedOutput);
})
.catch(error => {
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
updateModalContent(formattedOutput);
});
})
.catch(error => {
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
updateModalContent(formattedOutput);
});
})
.catch(error => {
formattedOutput += '\n❌ ' + _('Check failed: ') + (error.name === 'AbortError' ? _('timeout') : error.message) + '\n';
updateModalContent(formattedOutput);
});
} catch (error) {
formattedOutput += '\n❌ ' + _('Check failed: ') + error.message + '\n';
updateModalContent(formattedOutput);
}
});
} else {
safeExec('/usr/bin/podkop', [command], 0, res => {
formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
updateModalContent(formattedOutput);
});
}
} catch (error) {
updateModalContent(_('Error: ') + error.message);
}
}
// Button Factory
const ButtonFactory = {
createButton: function (config) {
return E('button', {
'class': `btn ${config.additionalClass || ''}`.trim(),
'click': config.onClick,
'style': config.style || ''
}, _(config.label));
},
createActionButton: function (config) {
return this.createButton({
label: config.label,
additionalClass: `cbi-button-${config.type || ''}`,
onClick: () => safeExec('/usr/bin/podkop', [config.action])
.then(() => config.reload && location.reload()),
style: config.style
});
},
createInitActionButton: function (config) {
return this.createButton({
label: config.label,
additionalClass: `cbi-button-${config.type || ''}`,
onClick: () => safeExec('/etc/init.d/podkop', [config.action])
.then(() => config.reload && location.reload()),
style: config.style
});
},
createModalButton: function (config) {
return this.createButton({
label: config.label,
onClick: () => showConfigModal(config.command, config.title),
additionalClass: `cbi-button-${config.type || ''}`,
style: config.style
});
}
};
// Create a loading placeholder for status text
function createLoadingStatusText() {
return E('span', { 'class': 'loading-indicator' }, _('Loading...'));
}
// Create the status section with buttons loaded immediately but status indicators loading asynchronously
let createStatusSection = async function () {
// Get initial podkop status
let initialPodkopStatus = { enabled: false };
try {
const result = await fs.exec('/usr/bin/podkop', ['get_status']);
if (result && result.stdout) {
const status = JSON.parse(result.stdout);
initialPodkopStatus.enabled = status.enabled === 1;
}
} catch (e) {
console.error('Error getting initial podkop status:', e);
}
return E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'table', style: 'display: flex; gap: 20px;' }, [
// Podkop Status Panel
E('div', { 'id': 'podkop-status-panel', 'class': 'panel', 'style': 'flex: 1; padding: 15px;' }, [
E('div', { 'class': 'panel-heading' }, [
E('strong', {}, _('Podkop Status')),
E('br'),
E('span', { 'id': 'podkop-status-text' }, createLoadingStatusText())
]),
E('div', { 'class': 'panel-body', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [
ButtonFactory.createActionButton({
label: 'Restart Podkop',
type: 'apply',
action: 'restart',
reload: true
}),
ButtonFactory.createActionButton({
label: 'Stop Podkop',
type: 'apply',
action: 'stop',
reload: true
}),
// Autostart button - create with initial state
ButtonFactory.createInitActionButton({
label: initialPodkopStatus.enabled ? 'Disable Autostart' : 'Enable Autostart',
type: initialPodkopStatus.enabled ? 'remove' : 'apply',
action: initialPodkopStatus.enabled ? 'disable' : 'enable',
reload: true
}),
ButtonFactory.createModalButton({
label: _('Global check'),
command: 'global_check',
title: _('Click here for all the info')
}),
ButtonFactory.createModalButton({
label: 'View Logs',
command: 'check_logs',
title: 'Podkop Logs'
}),
ButtonFactory.createModalButton({
label: _('Update Lists'),
command: 'list_update',
title: _('Lists Update Results')
})
])
]),
// Sing-box Status Panel
E('div', { 'id': 'singbox-status-panel', 'class': 'panel', 'style': 'flex: 1; padding: 15px;' }, [
E('div', { 'class': 'panel-heading' }, [
E('strong', {}, _('Sing-box Status')),
E('br'),
E('span', { 'id': 'singbox-status-text' }, createLoadingStatusText())
]),
E('div', { 'class': 'panel-body', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [
ButtonFactory.createModalButton({
label: 'Show Config',
command: 'show_sing_box_config',
title: 'Sing-box Configuration'
}),
ButtonFactory.createModalButton({
label: 'View Logs',
command: 'check_sing_box_logs',
title: 'Sing-box Logs'
}),
ButtonFactory.createModalButton({
label: 'Check Connections',
command: 'check_sing_box_connections',
title: 'Active Connections'
}),
ButtonFactory.createModalButton({
label: _('Check NFT Rules'),
command: 'check_nft',
title: _('NFT Rules')
}),
ButtonFactory.createModalButton({
label: _('Check DNSMasq'),
command: 'check_dnsmasq',
title: _('DNSMasq Configuration')
})
])
]),
// FakeIP Status Panel
E('div', { 'id': 'fakeip-status-panel', 'class': 'panel', 'style': 'flex: 1; padding: 15px;' }, [
E('div', { 'class': 'panel-heading' }, [
E('strong', {}, _('FakeIP Status'))
]),
E('div', { 'class': 'panel-body', 'style': 'display: flex; flex-direction: column; gap: 8px;' }, [
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', {}, [
E('span', { 'id': 'fakeip-browser-status' }, createLoadingStatusText())
]),
E('div', {}, [
E('span', { 'id': 'fakeip-router-status' }, createLoadingStatusText())
])
]),
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', {}, [
E('strong', {}, _('DNS Status')),
E('br'),
E('span', { 'id': 'dns-remote-status' }, createLoadingStatusText()),
E('br'),
E('span', { 'id': 'dns-local-status' }, createLoadingStatusText())
])
]),
E('div', { style: 'margin-bottom: 5px;' }, [
E('div', {}, [
E('strong', { 'id': 'config-name-text' }, _('Main config')),
E('br'),
E('span', { 'id': 'bypass-status' }, createLoadingStatusText())
])
])
])
]),
// Version Information Panel
E('div', { 'id': 'version-info-panel', 'class': 'panel', 'style': 'flex: 1; padding: 15px;' }, [
E('div', { 'class': 'panel-heading' }, [
E('strong', {}, _('Version Information'))
]),
E('div', { 'class': 'panel-body' }, [
E('div', { 'style': 'margin-top: 10px; font-family: monospace; white-space: pre-wrap;' }, [
E('strong', {}, _('Podkop: ')), E('span', { 'id': 'podkop-version' }, _('Loading...')), '\n',
E('strong', {}, _('LuCI App: ')), E('span', { 'id': 'luci-version' }, _('Loading...')), '\n',
E('strong', {}, _('Sing-box: ')), E('span', { 'id': 'singbox-version' }, _('Loading...')), '\n',
E('strong', {}, _('OpenWrt Version: ')), E('span', { 'id': 'openwrt-version' }, _('Loading...')), '\n',
E('strong', {}, _('Device Model: ')), E('span', { 'id': 'device-model' }, _('Loading...'))
])
])
])
])
]);
};
// Global variables for tracking state
let diagnosticsUpdateTimer = null;
let isInitialCheck = true;
showConfigModal.busy = false;
function startDiagnosticsUpdates() {
if (diagnosticsUpdateTimer) {
clearInterval(diagnosticsUpdateTimer);
}
// Immediately update when started
updateDiagnostics();
// Then set up periodic updates
diagnosticsUpdateTimer = setInterval(updateDiagnostics, constants.DIAGNOSTICS_UPDATE_INTERVAL);
}
function stopDiagnosticsUpdates() {
if (diagnosticsUpdateTimer) {
clearInterval(diagnosticsUpdateTimer);
diagnosticsUpdateTimer = null;
}
}
// Update individual text element with new content
function updateTextElement(elementId, content) {
const element = document.getElementById(elementId);
if (element) {
element.innerHTML = '';
element.appendChild(content);
}
}
async function updateDiagnostics() {
// Podkop Status check
safeExec('/usr/bin/podkop', ['get_status'], 0, result => {
try {
const parsedPodkopStatus = JSON.parse(result.stdout || '{"enabled":0,"status":"error"}');
// Update Podkop status text
updateTextElement('podkop-status-text',
E('span', {
'style': `color: ${parsedPodkopStatus.enabled ? constants.STATUS_COLORS.SUCCESS : constants.STATUS_COLORS.ERROR}`
}, [
parsedPodkopStatus.enabled ? '✔ Autostart enabled' : '✘ Autostart disabled'
])
);
// Update autostart button
const autostartButton = parsedPodkopStatus.enabled ?
ButtonFactory.createInitActionButton({
label: 'Disable Autostart',
type: 'remove',
action: 'disable',
reload: true
}) :
ButtonFactory.createInitActionButton({
label: 'Enable Autostart',
type: 'apply',
action: 'enable',
reload: true
});
// Find the autostart button and replace it
const panel = document.getElementById('podkop-status-panel');
if (panel) {
const buttons = panel.querySelectorAll('.cbi-button');
if (buttons.length >= 3) {
buttons[2].parentNode.replaceChild(autostartButton, buttons[2]);
}
}
} catch (error) {
updateTextElement('podkop-status-text',
E('span', { 'style': `color: ${constants.STATUS_COLORS.ERROR}` }, '✘ Error')
);
}
});
// Sing-box Status check
safeExec('/usr/bin/podkop', ['get_sing_box_status'], 0, result => {
try {
const parsedSingboxStatus = JSON.parse(result.stdout || '{"running":0,"enabled":0,"status":"error"}');
// Update Sing-box status text
updateTextElement('singbox-status-text',
E('span', {
'style': `color: ${parsedSingboxStatus.running && !parsedSingboxStatus.enabled ?
constants.STATUS_COLORS.SUCCESS : constants.STATUS_COLORS.ERROR}`
}, [
parsedSingboxStatus.running && !parsedSingboxStatus.enabled ?
'✔ running' : '✘ ' + parsedSingboxStatus.status
])
);
} catch (error) {
updateTextElement('singbox-status-text',
E('span', { 'style': `color: ${constants.STATUS_COLORS.ERROR}` }, '✘ Error')
);
}
});
// Version Information checks
safeExec('/usr/bin/podkop', ['show_version'], 2, result => {
updateTextElement('podkop-version',
document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown'))
);
});
safeExec('/usr/bin/podkop', ['show_luci_version'], 2, result => {
updateTextElement('luci-version',
document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown'))
);
});
safeExec('/usr/bin/podkop', ['show_sing_box_version'], 2, result => {
updateTextElement('singbox-version',
document.createTextNode(result.stdout ? result.stdout.trim() : _('Unknown'))
);
});
safeExec('/usr/bin/podkop', ['show_system_info'], 2, result => {
if (result.stdout) {
updateTextElement('openwrt-version',
document.createTextNode(result.stdout.split('\n')[1].trim())
);
updateTextElement('device-model',
document.createTextNode(result.stdout.split('\n')[4].trim())
);
} else {
updateTextElement('openwrt-version', document.createTextNode(_('Unknown')));
updateTextElement('device-model', document.createTextNode(_('Unknown')));
}
});
// FakeIP and DNS status checks
runCheck(checkFakeIP, 3, result => {
updateTextElement('fakeip-browser-status',
E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
result.error ? 'check error' : result.state === 'working' ? _('works in browser') : _('not works in browser')
])
);
});
runCheck(checkFakeIPCLI, 3, result => {
updateTextElement('fakeip-router-status',
E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
result.error ? 'check error' : result.state === 'working' ? _('works on router') : _('not works on router')
])
);
});
runCheck(checkDNSAvailability, 4, result => {
if (result.error) {
updateTextElement('dns-remote-status',
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! DNS check error')
);
updateTextElement('dns-local-status',
E('span', { style: `color: ${constants.STATUS_COLORS.WARNING}` }, '! DNS check error')
);
} else {
updateTextElement('dns-remote-status',
E('span', { style: `color: ${result.remote.color}` }, [
result.remote.state === 'available' ? '✔ ' : result.remote.state === 'unavailable' ? '✘ ' : '! ',
result.remote.message
])
);
updateTextElement('dns-local-status',
E('span', { style: `color: ${result.local.color}` }, [
result.local.state === 'available' ? '✔ ' : result.local.state === 'unavailable' ? '✘ ' : '! ',
result.local.message
])
);
}
});
runCheck(checkBypass, 1, result => {
updateTextElement('bypass-status',
E('span', { style: `color: ${result.error ? constants.STATUS_COLORS.WARNING : result.color}` }, [
result.error ? '! ' : result.state === 'working' ? '✔ ' : result.state === 'not_working' ? '✘ ' : '! ',
result.error ? 'check error' : result.message
])
);
});
// Config name
runAsyncTask(async () => {
try {
let configName = _('Main config');
const data = await uci.load('podkop');
const proxyString = uci.get('podkop', 'main', 'proxy_string');
if (proxyString) {
const activeConfig = proxyString.split('\n')
.map(line => line.trim())
.find(line => line && !line.startsWith('//'));
if (activeConfig) {
if (activeConfig.includes('#')) {
const label = activeConfig.split('#').pop();
if (label && label.trim()) {
configName = _('Config: ') + decodeURIComponent(label);
}
}
}
}
updateTextElement('config-name-text', document.createTextNode(configName));
} catch (e) {
console.error('Error getting config name from UCI:', e);
}
}, 1);
}
function createDiagnosticsSection(mainSection) {
let o = mainSection.tab('diagnostics', _('Diagnostics'));
o = mainSection.taboption('diagnostics', form.DummyValue, '_status');
o.rawhtml = true;
o.cfgvalue = () => E('div', {
id: 'diagnostics-status',
'data-loading': 'true'
});
}
function setupDiagnosticsEventHandlers(node) {
const titleDiv = E('h2', { 'class': 'cbi-map-title' }, _('Podkop'));
node.insertBefore(titleDiv, node.firstChild);
// Function to initialize diagnostics
function initDiagnostics(container) {
if (container && container.hasAttribute('data-loading')) {
container.innerHTML = '';
showConfigModal.busy = false;
createStatusSection().then(section => {
container.appendChild(section);
startDiagnosticsUpdates();
// Start error polling when diagnostics tab is active
utils.startErrorPolling();
});
}
}
document.addEventListener('visibilitychange', function () {
const diagnosticsContainer = document.getElementById('diagnostics-status');
const diagnosticsTab = document.querySelector('.cbi-tab[data-tab="diagnostics"]');
if (document.hidden || !diagnosticsTab || !diagnosticsTab.classList.contains('cbi-tab-active')) {
stopDiagnosticsUpdates();
// Don't stop error polling here - it's managed in podkop.js for all tabs
} else if (diagnosticsContainer && diagnosticsContainer.hasAttribute('data-loading')) {
startDiagnosticsUpdates();
// Ensure error polling is running when diagnostics tab is active
utils.startErrorPolling();
}
});
setTimeout(() => {
const diagnosticsContainer = document.getElementById('diagnostics-status');
const diagnosticsTab = document.querySelector('.cbi-tab[data-tab="diagnostics"]');
const otherTabs = document.querySelectorAll('.cbi-tab:not([data-tab="diagnostics"])');
// Check for direct page load case
const noActiveTabsExist = !Array.from(otherTabs).some(tab => tab.classList.contains('cbi-tab-active'));
if (diagnosticsContainer && diagnosticsTab && (diagnosticsTab.classList.contains('cbi-tab-active') || noActiveTabsExist)) {
initDiagnostics(diagnosticsContainer);
}
const tabs = node.querySelectorAll('.cbi-tabmenu');
if (tabs.length > 0) {
tabs[0].addEventListener('click', function (e) {
const tab = e.target.closest('.cbi-tab');
if (tab) {
const tabName = tab.getAttribute('data-tab');
if (tabName === 'diagnostics') {
const container = document.getElementById('diagnostics-status');
container.setAttribute('data-loading', 'true');
initDiagnostics(container);
} else {
stopDiagnosticsUpdates();
// Don't stop error polling - it should continue on all tabs
}
}
});
}
}, constants.DIAGNOSTICS_INITIAL_DELAY);
node.classList.add('fade-in');
return node;
}
return baseclass.extend({
createDiagnosticsSection,
setupDiagnosticsEventHandlers
});

View File

@@ -0,0 +1,146 @@
'use strict';
'require baseclass';
'require ui';
'require fs';
'require view.podkop.constants as constants';
// Flag to track if this is the first error check
let isInitialCheck = true;
// Set to track which errors we've already seen
const lastErrorsSet = new Set();
// Timer for periodic error polling
let errorPollTimer = null;
// Helper function to fetch errors from the podkop command
async function getPodkopErrors() {
return new Promise(resolve => {
safeExec('/usr/bin/podkop', ['check_logs'], 0, result => {
if (!result || !result.stdout) return resolve([]);
const logs = result.stdout.split('\n');
const errors = logs.filter(log =>
log.includes('[critical]')
);
resolve(errors);
});
});
}
// Show error notification to the user
function showErrorNotification(error, isMultiple = false) {
const notificationContent = E('div', { 'class': 'alert-message error' }, [
E('pre', { 'class': 'error-log' }, error)
]);
ui.addNotification(null, notificationContent);
}
// Helper function for command execution with prioritization
function safeExec(command, args, priority, callback, timeout = constants.COMMAND_TIMEOUT) {
priority = (typeof priority === 'number') ? priority : 0;
const executeCommand = async () => {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const result = await Promise.race([
fs.exec(command, args),
new Promise((_, reject) => {
controller.signal.addEventListener('abort', () => {
reject(new Error('Command execution timed out'));
});
})
]);
clearTimeout(timeoutId);
if (callback && typeof callback === 'function') {
callback(result);
}
return result;
} catch (error) {
console.warn(`Command execution failed or timed out: ${command} ${args.join(' ')}`);
const errorResult = { stdout: '', stderr: error.message, error: error };
if (callback && typeof callback === 'function') {
callback(errorResult);
}
return errorResult;
}
};
if (callback && typeof callback === 'function') {
setTimeout(executeCommand, constants.RUN_PRIORITY[priority]);
return;
}
else {
return executeCommand();
}
}
// Check for critical errors and show notifications
async function checkForCriticalErrors() {
try {
const errors = await getPodkopErrors();
if (errors && errors.length > 0) {
// Filter out errors we've already seen
const newErrors = errors.filter(error => !lastErrorsSet.has(error));
if (newErrors.length > 0) {
// On initial check, just store errors without showing notifications
if (!isInitialCheck) {
// Show each new error as a notification
newErrors.forEach(error => {
showErrorNotification(error, newErrors.length > 1);
});
}
// Add new errors to our set of seen errors
newErrors.forEach(error => lastErrorsSet.add(error));
}
}
// After first check, mark as no longer initial
isInitialCheck = false;
} catch (error) {
console.error('Error checking for critical messages:', error);
}
}
// Start polling for errors at regular intervals
function startErrorPolling() {
if (errorPollTimer) {
clearInterval(errorPollTimer);
}
// Reset initial check flag to make sure we show errors
isInitialCheck = false;
// Immediately check for errors on start
checkForCriticalErrors();
// Then set up periodic checks
errorPollTimer = setInterval(checkForCriticalErrors, constants.ERROR_POLL_INTERVAL);
}
// Stop polling for errors
function stopErrorPolling() {
if (errorPollTimer) {
clearInterval(errorPollTimer);
errorPollTimer = null;
}
}
return baseclass.extend({
startErrorPolling,
stopErrorPolling,
checkForCriticalErrors,
safeExec
});

View File

@@ -40,8 +40,8 @@ msgstr "Конфигурация Outbound"
msgid "Proxy Configuration URL" msgid "Proxy Configuration URL"
msgstr "URL конфигурации прокси" msgstr "URL конфигурации прокси"
msgid "Enter connection string starting with vless:// or ss:// for proxy configuration" msgid "Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for saving other configs"
msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси" msgstr "Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для сохранения других конфигураций"
msgid "Outbound Configuration" msgid "Outbound Configuration"
msgstr "Конфигурация исходящего соединения" msgstr "Конфигурация исходящего соединения"
@@ -88,8 +88,8 @@ msgstr "Введите имена доменов без протоколов (п
msgid "User Domains List" msgid "User Domains List"
msgstr "Список пользовательских доменов" msgstr "Список пользовательских доменов"
msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)" msgid "Enter domain names separated by comma, space or newline. You can add comments after //"
msgstr "Введите имена доменов через запятую, пробел или новую строку (пример: sub.example.com, example.com или один домен на строку)" msgstr "Введите имена доменов, разделяя их запятой, пробелом или с новой строки. Вы можете добавлять комментарии после //"
msgid "Local Domain Lists" msgid "Local Domain Lists"
msgstr "Локальные списки доменов" msgstr "Локальные списки доменов"
@@ -494,4 +494,382 @@ msgid "Update Lists"
msgstr "Обновить списки" msgstr "Обновить списки"
msgid "Lists Update Results" msgid "Lists Update Results"
msgstr "Результаты обновления списков" msgstr "Результаты обновления списков"
msgid "DNS Protocol Type"
msgstr "Тип DNS протокола"
msgid "Select DNS protocol to use"
msgstr "Выберите протокол DNS"
msgid "DNS over HTTPS (DoH)"
msgstr "DNS через HTTPS (DoH)"
msgid "DNS over TLS (DoT)"
msgstr "DNS через TLS (DoT)"
msgid "UDP (Unprotected DNS)"
msgstr "UDP (Незащищённый DNS)"
msgid "DNS Server"
msgstr "DNS сервер"
msgid "Select or enter DNS server address"
msgstr "Выберите или введите адрес DNS сервера"
msgid "DNS server address cannot be empty"
msgstr "Адрес DNS сервера не может быть пустым"
msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com"
msgstr "Неверный формат DNS сервера. Примеры: 8.8.8.8 или dns.example.com"
msgid "DNS Rewrite TTL"
msgstr "Перезапись TTL для DNS"
msgid "Time in seconds for DNS record caching (default: 600)"
msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 600)"
msgid "TTL value cannot be empty"
msgstr "Значение TTL не может быть пустым"
msgid "TTL must be a positive number"
msgstr "TTL должно быть положительным числом"
msgid "Cache File Path"
msgstr "Путь к файлу кэша"
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
msgstr "Выберите или введите путь к файлу кэша sing-box. Меняйте ТОЛЬКО если знаете, что делаете"
msgid "Cache file path cannot be empty"
msgstr "Путь к файлу кэша не может быть пустым"
msgid "Path must be absolute (start with /)"
msgstr "Путь должен быть абсолютным (начинаться с /)"
msgid "Path must end with cache.db"
msgstr "Путь должен заканчиваться на cache.db"
msgid "Path must contain at least one directory (like /tmp/cache.db)"
msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)"
msgid "Invalid path format. Must be like /tmp/cache.db"
msgstr "Неверный формат пути. Пример: /tmp/cache.db"
msgid "Select the network interface from which the traffic will originate"
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
msgid "Copy to Clipboard"
msgstr "Копировать в буфер обмена"
msgid "Close"
msgstr "Закрыть"
msgid "Loading..."
msgstr "Загрузка..."
msgid "Loading version information..."
msgstr "Загрузка информации о версии..."
msgid "Checking FakeIP..."
msgstr "Проверка FakeIP..."
msgid "timeout"
msgstr "таймаут"
msgid "Current config: "
msgstr "Текущая конфигурация: "
msgid "Invalid VLESS URL: type must be one of tcp, udp, grpc, http"
msgstr "Неверный URL VLESS: тип должен быть одним из tcp, udp, grpc, http"
msgid "Invalid VLESS URL: security must be one of tls, reality, none"
msgstr "Неверный URL VLESS: security должен быть одним из tls, reality, none"
msgid "Podkop"
msgstr "Podkop"
msgid "Proxy"
msgstr "Прокси"
msgid "VPN"
msgstr "VPN"
msgid "http://openwrt.lan:9090/ui"
msgstr "http://openwrt.lan:9090/ui"
msgid "Podkop Configuration"
msgstr "Конфигурация Podkop"
msgid "Active Connections"
msgstr "Активные соединения"
msgid "DNSMasq Configuration"
msgstr "Конфигурация DNSMasq"
msgid "Sing-box Configuration"
msgstr "Конфигурация Sing-box"
msgid "Extra configurations"
msgstr "Дополнительные конфигурации"
msgid "Add Section"
msgstr "Добавить раздел"
msgid "No output"
msgstr "Нет вывода"
msgid "Failed to copy: "
msgstr "Не удалось скопировать: "
msgid "Show Config"
msgstr "Показать конфигурацию"
msgid "View Logs"
msgstr "Просмотр логов"
msgid "Check Connections"
msgstr "Проверить соединения"
msgid "FakeIP Status"
msgstr "Статус FakeIP"
msgid "Device Model: "
msgstr "Модель устройства: "
msgid "OpenWrt Version: "
msgstr "Версия OpenWrt: "
msgid "Check DNSMasq"
msgstr "Проверить DNSMasq"
msgid "Check NFT Rules"
msgstr "Проверить правила NFT"
msgid "Update Lists"
msgstr "Обновить списки"
msgid "Lists Update Results"
msgstr "Результаты обновления списков"
msgid "NFT Rules"
msgstr "Правила NFT"
msgid "GitHub Connectivity"
msgstr "Подключение к GitHub"
msgid "Check GitHub"
msgstr "Проверить GitHub"
msgid "GitHub Connectivity Results"
msgstr "Результаты проверки подключения к GitHub"
msgid "Sing-Box Logs"
msgstr "Логи Sing-Box"
msgid "View recent sing-box logs from system journal"
msgstr "Просмотр последних логов sing-box из системного журнала"
msgid "View Sing-Box Logs"
msgstr "Просмотр логов Sing-Box"
msgid "Podkop Logs"
msgstr "Логи Podkop"
msgid "View recent podkop logs from system journal"
msgstr "Просмотр последних логов podkop из системного журнала"
msgid "View Podkop Logs"
msgstr "Просмотр логов Podkop"
msgid "Active Connections"
msgstr "Активные соединения"
msgid "View active sing-box network connections"
msgstr "Просмотр активных сетевых подключений sing-box"
msgid "DNSMasq Configuration"
msgstr "Конфигурация DNSMasq"
msgid "View current DNSMasq configuration settings"
msgstr "Просмотр текущих настроек конфигурации DNSMasq"
msgid "Sing-Box Configuration"
msgstr "Конфигурация Sing-Box"
msgid "Show current sing-box configuration"
msgstr "Показать текущую конфигурацию sing-box"
msgid "Show Sing-Box Config"
msgstr "Показать конфигурацию Sing-Box"
msgid "Diagnostic Tools"
msgstr "Инструменты диагностики"
msgid "Unknown"
msgstr "Неизвестно"
msgid "sing-box not running"
msgstr "sing-box не запущен"
msgid "DNS not configured"
msgstr "DNS не настроен"
msgid "running & enabled"
msgstr "запущен и активирован"
msgid "running but disabled"
msgstr "запущен, но деактивирован"
msgid "stopped but enabled"
msgstr "остановлен, но активирован"
msgid "stopped & disabled"
msgstr "остановлен и деактивирован"
msgid "works in browser"
msgstr "работает в браузере"
msgid "works on router"
msgstr "работает на роутере"
msgid "Check Router FakeIP"
msgstr "Проверить FakeIP на роутере"
msgid "FakeIP Router Check"
msgstr "Проверка FakeIP на роутере"
msgid "FakeIP CLI Check"
msgstr "Проверка FakeIP через CLI"
msgid "FakeIP CLI Check Results"
msgstr "Результаты проверки FakeIP через CLI"
msgid "not works in browser"
msgstr "не работает в браузере"
msgid "not works on router"
msgstr "не работает на роутере"
msgid "Diagnostics"
msgstr "Диагностика"
msgid "DNS Status"
msgstr "Статус DNS"
msgid "Bypass Status"
msgstr "Статус обхода"
msgid "proxy working correctly"
msgstr "прокси работает корректно"
msgid "vpn working correctly"
msgstr "vpn работает корректно"
msgid "proxy not working"
msgstr "прокси не работает"
msgid "vpn not working"
msgstr "vpn не работает"
msgid "proxy not running"
msgstr "прокси не запущен"
msgid "vpn not running"
msgstr "vpn не запущен"
msgid "proxy routing incorrect"
msgstr "маршрутизация прокси некорректна"
msgid "vpn routing incorrect"
msgstr "маршрутизация vpn некорректна"
msgid "First endpoint check failed"
msgstr "Проверка первой конечной точки не удалась"
msgid "IP comparison failed"
msgstr "Сравнение IP-адресов не удалось"
msgid "Bypass check error"
msgstr "Ошибка проверки обхода"
msgid "Main config"
msgstr "Основная конфигурация"
msgid "Config without description"
msgstr "Конфигурация без описания"
msgid "DNS working"
msgstr "DNS работает"
msgid "Router DNS working"
msgstr "DNS роутера работает"
msgid "Router DNS not working"
msgstr "DNS роутера не работает"
msgid "DNS check error"
msgstr "Ошибка проверки DNS"
msgid "available"
msgstr "доступен"
msgid "unavailable"
msgstr "недоступен"
msgid "Apply for SS2022"
msgstr "Применить для SS2022"
msgid "PODKOP CONFIGURATION"
msgstr "КОНФИГУРАЦИЯ PODKOP"
msgid "FAKEIP ROUTER TEST"
msgstr "ПРОВЕРКА FAKEIP НА РОУТЕРЕ"
msgid "FAKEIP BROWSER TEST"
msgstr "ПРОВЕРКА FAKEIP В БРАУЗЕРЕ"
msgid "FakeIP is working correctly on router (198.18.x.x)"
msgstr "FakeIP работает корректно на роутере (198.18.x.x)"
msgid "Click here for all the info"
msgstr "Нажмите для просмотра всей информации"
msgid "Check DNS server on current device (PC, phone)"
msgstr "Проверьте DNS сервер на текущем устройстве (ПК, телефон)"
msgid "Its must be router!"
msgstr "Это должен быть роутер!"
msgid "Global check"
msgstr "Глобальная проверка"
msgid "Starting lists update..."
msgstr "Начало обновления списков..."
msgid "DNS check passed"
msgstr "Проверка DNS пройдена"
msgid "DNS check failed after 60 attempts"
msgstr "Проверка DNS не удалась после 60 попыток"
msgid "GitHub connection check passed"
msgstr "Проверка подключения к GitHub пройдена"
msgid "GitHub connection check passed (via proxy)"
msgstr "Проверка подключения к GitHub пройдена (через прокси)"
msgid "GitHub connection check failed after 60 attempts"
msgstr "Проверка подключения к GitHub не удалась после 60 попыток"
msgid "Downloading and processing lists..."
msgstr "Загрузка и обработка списков..."
msgid "Lists update completed successfully"
msgstr "Обновление списков успешно завершено"
msgid "Lists update failed"
msgstr "Обновление списков не удалось"
msgid "Error: "
msgstr "Ошибка: "

View File

@@ -854,4 +854,376 @@ msgid "Check completed"
msgstr "" msgstr ""
msgid "Check failed" msgid "Check failed"
msgstr ""
msgid "DNS Protocol Type"
msgstr ""
msgid "Select DNS protocol to use"
msgstr ""
msgid "DNS over HTTPS (DoH)"
msgstr ""
msgid "DNS over TLS (DoT)"
msgstr ""
msgid "UDP (Unprotected DNS)"
msgstr ""
msgid "DNS Server"
msgstr ""
msgid "Select or enter DNS server address"
msgstr ""
msgid "DNS Rewrite TTL"
msgstr ""
msgid "Time in seconds for DNS record caching (default: 600)"
msgstr ""
msgid "TTL value cannot be empty"
msgstr ""
msgid "TTL must be a positive number"
msgstr ""
msgid "Cache File Path"
msgstr ""
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
msgstr ""
msgid "Cache file path cannot be empty"
msgstr ""
msgid "Path must be absolute (start with /)"
msgstr ""
msgid "Path must end with cache.db"
msgstr ""
msgid "Path must contain at least one directory (like /tmp/cache.db)"
msgstr ""
msgid "Invalid path format. Must be like /tmp/cache.db"
msgstr ""
msgid "Copy to Clipboard"
msgstr ""
msgid "Close"
msgstr ""
msgid "Loading..."
msgstr ""
msgid "Loading version information..."
msgstr ""
msgid "Checking FakeIP..."
msgstr ""
msgid "timeout"
msgstr ""
msgid "Current config: "
msgstr ""
msgid "Invalid VLESS URL: type must be one of tcp, udp, grpc, http"
msgstr ""
msgid "Invalid VLESS URL: security must be one of tls, reality, none"
msgstr ""
msgid "Podkop"
msgstr ""
msgid "Proxy"
msgstr ""
msgid "VPN"
msgstr ""
msgid "http://openwrt.lan:9090/ui"
msgstr ""
msgid "Podkop Configuration"
msgstr ""
msgid "Active Connections"
msgstr ""
msgid "DNSMasq Configuration"
msgstr ""
msgid "Sing-box Configuration"
msgstr ""
msgid "Extra configurations"
msgstr ""
msgid "Add Section"
msgstr ""
msgid "No output"
msgstr ""
msgid "Failed to copy: "
msgstr ""
msgid "Show Config"
msgstr ""
msgid "View Logs"
msgstr ""
msgid "Check Connections"
msgstr ""
msgid "FakeIP Status"
msgstr ""
msgid "Device Model: "
msgstr ""
msgid "OpenWrt Version: "
msgstr ""
msgid "Check DNSMasq"
msgstr ""
msgid "Check NFT Rules"
msgstr ""
msgid "Update Lists"
msgstr ""
msgid "Lists Update Results"
msgstr ""
msgid "NFT Rules"
msgstr ""
msgid "GitHub Connectivity"
msgstr ""
msgid "Check GitHub"
msgstr ""
msgid "GitHub Connectivity Results"
msgstr ""
msgid "Sing-Box Logs"
msgstr ""
msgid "View recent sing-box logs from system journal"
msgstr ""
msgid "View Sing-Box Logs"
msgstr ""
msgid "Podkop Logs"
msgstr ""
msgid "View recent podkop logs from system journal"
msgstr ""
msgid "View Podkop Logs"
msgstr ""
msgid "Active Connections"
msgstr ""
msgid "View active sing-box network connections"
msgstr ""
msgid "DNSMasq Configuration"
msgstr ""
msgid "View current DNSMasq configuration settings"
msgstr ""
msgid "Sing-Box Configuration"
msgstr ""
msgid "Show current sing-box configuration"
msgstr ""
msgid "Show Sing-Box Config"
msgstr ""
msgid "Diagnostic Tools"
msgstr ""
msgid "Unknown"
msgstr ""
msgid "sing-box not running"
msgstr ""
msgid "DNS not configured"
msgstr ""
msgid "running & enabled"
msgstr ""
msgid "running but disabled"
msgstr ""
msgid "stopped but enabled"
msgstr ""
msgid "stopped & disabled"
msgstr ""
msgid "works in browser"
msgstr ""
msgid "works on router"
msgstr ""
msgid "Check Router FakeIP"
msgstr ""
msgid "FakeIP Router Check"
msgstr ""
msgid "FakeIP CLI Check"
msgstr ""
msgid "FakeIP CLI Check Results"
msgstr ""
msgid "not works in browser"
msgstr ""
msgid "not works on router"
msgstr ""
msgid "Diagnostics"
msgstr ""
msgid "DNS Status"
msgstr ""
msgid "Bypass Status"
msgstr ""
msgid "proxy working correctly"
msgstr ""
msgid "vpn working correctly"
msgstr ""
msgid "proxy not working"
msgstr ""
msgid "vpn not working"
msgstr ""
msgid "proxy not running"
msgstr ""
msgid "vpn not running"
msgstr ""
msgid "proxy routing incorrect"
msgstr ""
msgid "vpn routing incorrect"
msgstr ""
msgid "First endpoint check failed"
msgstr ""
msgid "IP comparison failed"
msgstr ""
msgid "Bypass check error"
msgstr ""
msgid "Main config"
msgstr ""
msgid "Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs"
msgstr ""
msgid "Config without description"
msgstr ""
msgid "DNS working"
msgstr ""
msgid "Router DNS working"
msgstr ""
msgid "Router DNS not working"
msgstr ""
msgid "DNS check error"
msgstr ""
msgid "available"
msgstr ""
msgid "unavailable"
msgstr ""
msgid "PODKOP CONFIGURATION"
msgstr ""
msgid "FAKEIP ROUTER TEST"
msgstr ""
msgid "FAKEIP BROWSER TEST"
msgstr ""
msgid "FakeIP is working correctly on router (198.18.x.x)"
msgstr ""
msgid "Click here for all the info"
msgstr ""
msgid "Check DNS server on current device (PC, phone)"
msgstr ""
msgid "Its must be router!"
msgstr ""
msgid "Global check"
msgstr ""
msgid "Starting lists update..."
msgstr ""
msgid "DNS check passed"
msgstr ""
msgid "DNS check failed after 60 attempts"
msgstr ""
msgid "GitHub connection check passed"
msgstr ""
msgid "GitHub connection check passed (via proxy)"
msgstr ""
msgid "GitHub connection check failed after 60 attempts"
msgstr ""
msgid "Downloading and processing lists..."
msgstr ""
msgid "Lists update completed successfully"
msgstr ""
msgid "Lists update failed"
msgstr ""
msgid "Loading..."
msgstr ""
msgid "Error: "
msgstr "" msgstr ""

View File

@@ -5,6 +5,9 @@
"file": { "file": {
"/etc/init.d/podkop": [ "/etc/init.d/podkop": [
"exec" "exec"
],
"/usr/bin/podkop": [
"exec"
] ]
}, },
"ubus": { "ubus": {

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=podkop PKG_NAME:=podkop
PKG_VERSION:=0.3.11 PKG_VERSION:=0.4.1
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=ITDog <podkop@itdog.info> PKG_MAINTAINER:=ITDog <podkop@itdog.info>
@@ -13,8 +13,9 @@ define Package/podkop
SECTION:=net SECTION:=net
CATEGORY:=Network CATEGORY:=Network
DEPENDS:=+sing-box +curl +jq +kmod-nft-tproxy +coreutils-base64 DEPENDS:=+sing-box +curl +jq +kmod-nft-tproxy +coreutils-base64
CONFLICTS:=https-dns-proxy
TITLE:=Domain routing app TITLE:=Domain routing app
URL:=https://itdog.info URL:=https://podkop.net
PKGARCH:=all PKGARCH:=all
endef endef
@@ -49,6 +50,9 @@ define Package/podkop/install
$(INSTALL_DIR) $(1)/etc/config $(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/etc/config/podkop $(1)/etc/config/podkop $(INSTALL_CONF) ./files/etc/config/podkop $(1)/etc/config/podkop
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) ./files/usr/bin/podkop $(1)/usr/bin/podkop
endef endef
$(eval $(call BuildPackage,podkop)) $(eval $(call BuildPackage,podkop))

View File

@@ -1,13 +1,13 @@
config main 'main' config main 'main'
option mode 'proxy' option mode 'proxy'
#option interface '' #option interface ''
option proxy_config_type '' option proxy_config_type 'url'
#option outbound_json '' #option outbound_json ''
option proxy_string '' option proxy_string ''
option domain_list_enabled '1' option domain_list_enabled '1'
option domain_list 'russia_inside' list domain_list 'russia_inside'
option subnets_list_enabled '0' option subnets_list_enabled '0'
option custom_domains_list_type 'disable' option custom_domains_list_type 'disabled'
#list custom_domains '' #list custom_domains ''
#option custom_domains_text '' #option custom_domains_text ''
option custom_local_domains_list_enabled '0' option custom_local_domains_list_enabled '0'
@@ -31,4 +31,12 @@ config main 'main'
option quic_disable '0' option quic_disable '0'
option dont_touch_dhcp '0' option dont_touch_dhcp '0'
option update_interval '1d' option update_interval '1d'
option custom_domains_text option dns_type 'doh'
option dns_server '8.8.8.8'
option dns_rewrite_ttl '60'
option cache_file '/tmp/cache.db'
list iface 'br-lan'
option mon_restart_ifaces '0'
#list restart_ifaces 'wan'
option ss_uot '0'
option detour '0'

File diff suppressed because it is too large Load Diff

2624
podkop/files/usr/bin/podkop Executable file

File diff suppressed because it is too large Load Diff